JPA의 변경감지 (DirtyChecking)와 @DynamicUpdate에 대해 알아보자
JPA에는 수정관련된 메서드가 존재하지 않는다.
JPA를 통해 데이터를 수정하려면, Entity를 조회하여 조회된 Entity의 데이터를 변경하면 DB에 자동으로 반영되는 기능을 Dirty Checking이라고 한다.
여기서 Dirty란 상태의 변화가 생긴 정도로, Dirty Checking은 상태 변경 검사를 말한다.
이 변화가 있다의 기준은, 최초 조회 상태이다.
@Transactional
public void modify(String nickname, String email) {
Optional<User> findUser = userRepository.findByEmail(email);
User user = findUser.orElseThrow(UserNotFoundException::new);
// nickname 변경
user.updateNickname(nickname);
}
이 과정에서 update쿼리를 요청하는 부분이 없지만,
JPA의 변경감지(Dirty Checking)으로 DB의 데이터가 변경된다.
JPA는 영속성 컨텍스트에 엔티티를 보관할때, 최초의 상태를 저장하고 있는다.
이를 스냅샷이라고 하고, 트랜잭션이 끝나는 시점에 스냅샷과 달라진 부분을 비교하여 Update 쿼리를 날리게된다.
[출처 : 김영한님의 자바 ORM 표준 JPA 프로그래밍]
영속성 컨텍스트에서 관리하는 객체에 정보가 변경되면, 1차 캐시에 저장된 스냅샷과 비교하고, 달라진 부분에 맞게 SQL을 쓰기지연 SQL 저장소에 저장한다. 이후 커밋되는 시점에 flush 한다.
테스트 코드를 작성해서 유저 닉네임을 수정해보자.
@Test
public void 유저닉네임변경() {
// given
User savedUser = userRepository.save(userA);
// when
String nickname = "닉네임수정";
savedUser.updateNickname(nickname);
// then
assertThat(savedUser.getNickname()).isEqualTo(nickname);
}
save 메서드를 호출하지 않았지만, 아래와 같이 변경된 닉네임으로 테스트를 통과하며 정상적으로 updaet가 수행된다.
이때, 이런 상태변경 검사(Dirty Checking)의 대상은 영속성 컨텍스트가 관리하는 엔티티에만 적용된다.
과 같은 준영속/비영속 상태의 엔티티는 Dirty Checking의 대상에 포함되지 않는다.
이때, 위에서 업데이트 시 변경한 내용은 nickname 하나인데, update 쿼리에는 User 엔티티의 모든 필드가 update되는 것을 확인할 수 있다.
기본적으로 JPA는 전체 필드를 업데이트 하는 방식으로, Dirty Checking으로 생성되는 update 쿼리는 기본적으로 모든 필드를 업데이트한다.
이렇게 JPA에서 변경된 필드만 update하지 않고,
모든 필드를 변경하는 쿼리를 생성하여 얻을 수 있는 장점으로,
이러한 이점으로 JPA는 Update시 모든 필드를 쿼리로 만들어 실행한다.
하지만, 하나의 엔티티에 필드가 많아져 update가 부담스러워지고,
변경된 필드만 upadte 쿼리로 적용하고 싶다면 @DynamicUpdate 어노테이션을 사용한다.
User엔티티에 @DynamicUpdate 어노테이션을 추가하고 동일한 테스트를 실행한결과, 변경된 nickname만 update쿼리로 적용되었다.