본문 바로가기

Programming/Java & JSP & Spring

JPA 변경감지(Dirty Checking)과 OSIV

JPA를 사용할 때 변경 감지를 주의해야한다.

아무런 생각없이 중간 데이터 전달 경로로 엔티티의 필드를 건드린다면, 실제로 DB에도 변경이 적용되기 때문이다.

 

또한 주의해야할 점은 OSIV를 활성화 시켰을 경우이다.

다음 예제를 살펴보자.

fun test() {
   val product1 = productItemRepository.findById(1L).orElse(null)
   product1.amount = product1.amount + 1 // 엔티티 값 변경
   // osiv가 활성화되어있다면 여기서 product1은 영속성컨텍스트에 계속 살아있게된다. (영속 상태)
   // osiv가 반대로 비활성화 되어있다면 여기서 product1은 영속성컨텍스트에 남아있지 않는다. (준영속 detach 상태)
   dirtyCheckingTxService.txCommit() // 트랜잭션을 만들어 커밋
   // 커밋을 이용하여 flush를 하면 jpa는 dirty checking(변경감지)를 수행하고 최초의 엔티티 스냅샷과 다르다면 update쿼리를 날리게된다.

   println("product1은 변경감지가 되었는가?")
}

위 test() 메소드는 트랜잭션이 없고, dirtyCheckingTxService 클래스에는 트랜잭션이 있는 상태이다.

product1을 조회했을 경우 OSIV가 비활성화 되어있다면 트랜잭션이 test() 메소드에는 존재하지 않기 때문에 영속성 컨텍스트에 남아있지 않는다.

dirtyCheckingTxService.txCommit()에서 아무런 작업없이 트랜잭션을 열고 닫아만 줘도 커밋 & 플러시를 수행하기 때문에 영속성 컨텍스트에 있는 엔티티를 변경감지해서 UPDATE쿼리를 날리게된다.

 

이러한 변경감지는 AUTO FLUSH일 때도 물론 일어난다. 주의해야한다.

다음 예를 살펴보자.

fun test() {
   val product1 = productItemRepository.findById(1L).orElse(null)
   product1.amount = product1.amount + 1 // 엔티티 값 변경
   // osiv가 활성화되어있다면 여기서 product1은 영속성컨텍스트에 계속 살아있게된다. (영속 상태)
   // osiv가 반대로 비활성화 되어있다면 여기서 product1은 영속성컨텍스트에 남아있지 않는다. (준영속 detach 상태)
        
   dirtyCheckingTxService.getByName("banana") // 바로 db select 쿼리를 날려서 auto-flush
   // tx가 readonly가 아니라면 dirty checking 수행
        
   println("product1은 변경감지가 되었는가?")
}

위와 같이 dirtyCheckingTxService에서 단순 조회를 하는 트랜잭션 서비스라고 가정하자.

SELECT쿼리를 날리기 전에 AUTO FLUSH를 수행하는데, 이때도 쓰기지연 SQL에 있는 쿼리만 날리는 것 뿐만 아니라, Dirty Checking도 수행한다.

단, 여기서 신기한 것은 주석에도 달아놨듯이 dirtyCheckingTxService가 readOnly 트랜잭션이라면 쓰기지연 SQL 쿼리만 Flush하고 Dirty Checking은 수행하지 않는다.

 

따라서 이러한 문제를 방지하기 위해서는 readOnly의 옵션을 활성화시키는 방법이 있을 것이다.

아니면 애초 문제인 OSIV를 비활성화 시키는 방법이 가장 좋아보이긴 하지만, 그럴 수 없는 경우가 대부분일 것이므로 위의 예제를 살펴보고 원치 않는 Dirty Checking을 주의하자.