Spring Data Jpa 같은 ORM 구현체를 사용하다보면 더티체킹 이라는 단어를 종종 듣게 됩니다.
@Log4j2
@RequiredArgsArgsConstructor
@Service
public class PayService{
public void updateNative(Long id, String tradeNo){
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작(1)
Pay pay = em.find(Pay.class,id); // (2)
pay.changeTradeNo(tradeNo); // 엔티티만 변경(3)
tx.commit(); // 트랜잭션 커밋(4)
코드로 보면 별도로 데이터베이스에는 save 하지 않습니다.
여기서 DB에 update 쿼리에 관한 코드는 어디에도 없습니다.
이 코드가 어떻게 작동하는지 테스트 코드
@SpringBootTest
public class PayServiceTest {
@Autowired
PayRepository payRepository;
@Autowired
PayService payService;
@After
public void tearDown() throws Exception {
payRepository.deleteAll();
}
@Test
public void entityManagerTest() {
//given
Pay pay = payRepository.save(new Pay("test1", 100));
//when
String updateTradeNo = "test2";
payService.updateNative(pay.getId(), updateTradeNo);
//then
Pay saved = payRepository.findAll().get(0);
assertThat(saved.getTradeNo()).isEqualTo(updateTradeNo);
}
}
테스트를 수행하면 아래와 같이 save 메서드로 변경사항을 저장하지 않았음에도 update 쿼리가 나갑니다.
이에 대한 이유는 바로 Dirty Checking 때문입니다.
JPA 에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터 베이스에 자동으로 반영해줍니다.
이때 변화가 있다의 기준은 최초 조회 상태입니다!!
JPA에서는 엔티티를 조회하면 해당 엔티티의 조회 상태 그대로 스냅샷
을 만들어 놓습니다.
그리고 트랜잭션이 끝나는 시점에서 이 스냅샷과 비교하여 다른점이 있다면 UpdateQuery를 DB에 전달합니다.
이런 상태 변경 검사의 대상은 영속성 컨테스트가 관리하는 엔티티에만 적용됩니다.
즉 값을 변경해도 DB에는 반영되지 않습니다.
전체 필드를 업데이트 하는 방식의 장점
- 생성되는 쿼리가 같아 부트 실행시점에 미리 만들어서 재사용이 가능합니다.
- 데이터베이스 입장에서 쿼리 재사용이 가능합니다.
( 동일한 쿼리를 받으면 이전에 파싱된 쿼리를 재사용합니다.)
하지만 필드가 2~30 개가 이상인 경우에는 이런 전체필드가 update 쿼리가 부담스러울 수 있습니다.
그래서 이런 경우에는 @DynamicUpdate
로 변경 필드만 반영되도록 할 수 있습니다.
아래와 같이 엔티티 최상단에 @DynamicUpdate
만 선언해주면 됩니다.
@Getter
@NoArgsConstructor
@Entity
@DynamicUpdate // 변경한 필드만 대응
public class Pay {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String tradeNo;
private long amount;