728x90

더티 체킹(Dirty Checking)은 트랜잭션 안에서 엔티티의 변경이 일어나면, 변경 내용을 자동으로 데이터 베이스에 반영하는 JPA의 특징 중 하나이다.

  • 우선 데이터베이스에 변경 데이터를 저장하는 시점은 Transaction Commit, EntityManager flush, JPQL 사용 이렇게 3가지가 있다.
  • JPA에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터 베이스에 자동으로 반영해준다.(변화의 기준은 최초 조회 상태)
  • JPA에서는 엔티티를 조회하면 해당 엔티티의 조회 상태 그대로 스냅숏을 만든다. 트랜잭션이 끝나는 시점에 스냅숏과 현재의 상태를 비교해 다른 점이 있다면 Update 쿼리를 데이터 베이스로 날린다.

이제 예시를 보며 더 자세하게 보자

 public void updateNative(Long id, String trade){
        EntityManager em = entityManagerFactory.createEntityManager();
        EntityTransaction ef = em.getTransaction();
        ef.begin();
        Payment payment = em.find(Payment.class, id);
        payment.changeTradeNumber(trade);
        ef.commit();
    }

코드를 보면 별도로 데이터베이스에 저장하지 않는다.

트랜잭션을 시작되고 엔티티 조회 후 엔티티값을 변경하고 트랜잭션을 커밋한다.

@Test
@DisplayName("엔티티 메니저로 확인")
void find_entityManager()throws Exception{
    //given
    Payment pay = paymentRepository.save(new Payment("test1",  100L));

    //when
    String updateTradeNo = "test2";
    paymentService.updateNative(pay.getId(), updateTradeNo);

    //then
    Payment saved = paymentRepository.findAll().get(0);
    Assertions.assertThat(saved.getTradeNumber()).isEqualTo(updateTradeNo);

}

위 테스트를 수행하면, 아래와 같은 로그를 확인할 수 있다.

Hibernate: 
    update 
    	payment 
    set 
        amount=?, 
        trade_number=? 
    where 
        id=?

save 메서드로 변경 사항을 저장하지 않았지만 update 쿼리가 실행된다.

이것이 더티체킹의 역할이다.

위에 소개에서 말한 것처럼 JPA에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터베이스에 자동으로 반영해준다.

여기서 JPA는 엔티티 조회를 하면 엔티티의 조회 상태를 스냅숏으로 만들어 놓고 트랜잭션이 끝나는 시점에는 조회 상태를 저장한 스냅숏과 비교해서 다른 점이 있으면 Update 쿼리를 데이터베이스로 전달한다.

여기서 더티 체킹(상태 변경 검사)의 대상은 영속성 콘텍스트가 관리하는 엔티티에만 적용된다.

데이터베이스에 반영되기 전 처음으로 생성된 엔티티나 detach 된 엔티티는 더티 체킹 대상에 들어가지 않는다.(값을 변경해도 데이터 베이스에 반영되지 않는다.)

 

 

이제 Spring Data Jpa와 @Transactional을 같이 사용하는 경우를 보자

@Transactional
public void update(Long id,String tradeNumber){
    Payment payment = paymentRepository.getOne(id);
    payment.changeTradeNumber(tradeNumber);
}
@Test
@DisplayName("SpringDataJpa로_확인")
void find_jpa() {
    //given
    Payment pay = paymentRepository.save(new Payment("test1",  100L));
    //when
    String updateTradeNo = "test2";
    paymentService.update(pay.getId(), updateTradeNo);
    //then
    Payment saved = paymentRepository.findAll().get(0);
    Assertions.assertThat(saved.getTradeNumber()).isEqualTo(updateTradeNo);
}
Hibernate: 
    update 
    	payment 
    set 
        amount=?, 
        trade_number=? 
    where 
        id=?

정상적으로 쿼리가 실행된다.

 

현재는 JPA에서 전체 필드를 업데이트하는 방식을 기본값으로 사용한다.

전체 필드를 업데이트를 하는 방식은 생성되는 쿼리가 같아 부트 실행 시점에 미리 만들어서 재사용이 가능하고, 데이터베이스 입장에서도 재사용이 가능하다.

다만, 필드가 많아질 경우 전체 필드에 대한 Update 쿼리는 부담스러울 수 있다.

이럴 경우 @DynamicUpdate로 변경 필드만 반영되도록 할 수 있다.

Hibernate:
        update 
             payment 
        set 
             trade_number=? 
        where 
              id=?

테스트 코드를 다시 실행해보면 변경한 trade_number만 update 쿼리에 반영된 것을 확인할 수 있다.

 

 

 

🧑🏻‍💻예제 코드: https://github.com/ryudongjae/blog-ex

 

GitHub - ryudongjae/blog-ex: 📁블로그 예제 코드

📁블로그 예제 코드 . Contribute to ryudongjae/blog-ex development by creating an account on GitHub.

github.com


REFERENCE

https://jojoldu.tistory.com/415

728x90