과정을 즐기자

JPA 변경감지 적용이 안될 때 본문

Spring Data

JPA 변경감지 적용이 안될 때

320Hwany 2023. 1. 2. 11:56

JPA 변경 감지 적용이 안된다?

프로젝트에서 리뷰 수정 기능을 구현하고 있는데 JPA에서는 변경 감지(더티 체킹)를 작동한다는 사실을 알고 있었다.
영속성이 유지되는 엔티티라면 update 쿼리를 따로 날려주지 않고 엔티티의 값을 변경하면 트랜잭션 커밋을 할 때
수정된 내용이 반영된다는 것이다. 이러한 사실을 알고 있었는데 테스트 코드 작성시 변경 감지가 적용이 안되었다.
왜 그럴까..? 우선 Controller, Service, Domain 코드를 살펴보자.

ReviewController

@RequiredArgsConstructor
@RestController
public class ReviewController {

    private final ReviewService reviewService;

    ...

    @PatchMapping("/review/{reviewId}")
    public ResponseEntity<ReviewResponse> updateReview(@PathVariable Long reviewId, 
                                @RequestBody @Valid ReviewUpdate reviewUpdate) {
        ReviewResponse reviewResponse = 
        reviewService.updateReview(reviewId, reviewUpdate);
        return ResponseEntity.ok(reviewResponse);
    }
}

ReviewService

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class ReviewService {

    private final ReviewRepository reviewRepository;

    ...

    @Transactional
    public ReviewResponse updateReview(Long id, ReviewUpdate reviewUpdate) {
        Review review = reviewRepository.findById(id)
                .orElseThrow(ReviewNotFoundException::new);
        review.update(reviewUpdate);
        return new ReviewResponse(review);
    }
}

Review

@Getter
@NoArgsConstructor
@Entity
public class Review {

   ...

   public void update(ReviewUpdate reviewUpdate) {
        this.title = reviewUpdate.getTitle();
        this.content = reviewUpdate.getContent();
        this.rating = reviewUpdate.getRating();
    }
}

reviewService.updateReview 에 분명히 @Transaction을 써주었다. 따라서 따로 update 쿼리를
만들 필요없이 위와 같이 엔티티의 값만 바꿔도 적용이 되어야 한다.

테스트 코드로 확인해보자.

@Test
@DisplayName("수정할 리뷰가 존재하고 조건에 만족하면 리뷰가 수정됩니다.")
void updateReviewSuccess() {
    // given
    Review review = Review.builder()
            .title("제목입니다.")
            .content("내용입니다.")
            .rating(3)
            .build();

    reviewRepository.save(review);

    ReviewUpdate reviewUpdate = ReviewUpdate.builder()
            .title("제목 수정입니다.")
            .content("내용 수정입니다.")
            .rating(5)
            .build();

    // when
    reviewService.updateReview(review.getId(), reviewUpdate);

    // then
    assertThat(reviewRepository.count()).isEqualTo(1);
    assertThat(review.getTitle()).isEqualTo("제목 수정입니다.");
    assertThat(review.getContent()).isEqualTo("내용 수정입니다.");
    assertThat(review.getRating()).isEqualTo(5);
}

위 테스트가 실패를 했다. 이유는 테스트 코드에도 @Transactional을 적용해줘야 하기 때문이다.
@Transaction을 적용해주니 테스트가 통과하였다. 하지만 이때 Service의 @Transaction을 지워도
테스트에 @Transaction이 있어서 테스트가 통과하였다. 애플리케이션의 작동 여부를 체크하기 위한
테스트 코드인데 뭔가 찝찝한 느낌이다... 혹시 다른 방법이 있으려나... 찾으면 다시 글을 작성하기로..

 

==========================================================================

더 좋은 방법을 찾았습니다!

 

테스트 코드 작성시 @Transactional 사용하지 않기

테스트 코드에서 @Transactional은 데이터베이스도 롤백이 되기 때문에 @BeforeEach에서 일일이 repository를 deleteAll 해주지 않고 외래키 문제로 고민할 필요도 없기 때문에 편리합니다. 그래서 자주 사용

320hwany.tistory.com

==========================================================================

 

다음으로 혹시모르니 Postman을 이용해 DB에 수정이 반영되는 지를 확인해보았다.

리뷰 저장

리뷰 수정

수정이 DB에 잘 반영되었다.  

 

결론 : 사실은 변경감지가 적용이 안된 것이 아니라 테스트를 작성할 때 반영이 안된 것이다.    

@Transaction의 적용 범위는 main과 test가 다르므로 필요한 경우 각각 달아주어야 한다.