Search

JPA Dirty Checking 이란?

Created time
2022/02/27 15:26
Modified
2022/11/14 12:56
Tags
jpa
JPA를 사용하면서 더티 체킹과 트랜잭션의 관계에 대해서 알고 있지 않으면,
비즈니스 로직에서 다루는 엔티티 데이터가 꼬이는 경우가 발생합니다.
데이터가 꼬이는 경우를 방지하려면,
더티 체킹(Dirty Checking)이 어떤 상황에 사용이 되는지 알고 있어야 합니다.
여기서는 더티 체킹이 어떤 상황에서 발생하는지 확인해보려고 합니다.

더티 체킹(Dirty Checking)이란?

JPA는 엔티티 매니저가 엔티티를 저장, 조회, 수정, 삭제를 합니다.
그런데 엔티티 매니저의 메서드를 찾아보면, 저장(persist), 조회(find), 수정(?), 삭제(delete)로 수정에 해당하는 메서드가 없습니다.
대신에 수정(?)에 해당하는 더티 체킹(Dirty Checking)을 지원합니다.
더티 체킹은 Transaction 안에서 엔티티의 변경이 일어나면, 변경 내용을 자동으로 데이터베이스에 반영하는 JPA 특징입니다.
데이터베이스에 변경 데이터를 저장하는 시점
1.
Transaction commit 시점
2.
EntityManager flush 시점
3.
JPQL 사용 시점
또한, 영속성 컨택스트(Persistence Context) 안에 있는 엔티티를 대상으로 더티 체킹이 일어납니다.
여기서 Dirty란 "엔티티 데이터의 변경된 부분"으로 해석하시면 됩니다. 즉, 변경된 부분을 체크해서 DB에 반영한다는 뜻으로 해석합니다.
JPA 영속성 컨텍스트에서 엔티티 내부 관리 방식
더티 체킹이 일어나는 환경은 아래 두 가지 조건이 충족되어야 합니다.
영속 상태(Managed) 안에 있는 엔티티인 경우
Transaction 안에서 엔티티를 변경하는 경우
Transaction은 두 가지 방식으로 사용할 수 있습니다.
1.
Service Layer에서 @Transactional을 사용하는 경우
2.
Entity Transaction을 이용해서 트랜잭션을 범위를 지정하고 사용하는 경우
여기서 위 1, 2번 Transaction 사용 예를 보이겠습니다.
1, 2번은 PK를 id로 가지고 있는 User의 name을 바꾸는 동일한 작업을 하겠습니다.

1. Service Layer에서 @Transactional을 사용하는 경우

@Service public class ExampleService { @Transactional public void updateInfo(Long id, String name) { User user = userRepository.findById(id); user.setName(name); } }
Java
복사

2. Entity Transaction을 이용해서 트랜잭션을 범위를 지정하고 사용하는 경우

@Service public class ExampleService { // 2번에서는 자세하게 실제 작동 방식에 대해서 주석으로 설명하겠습니다. public void updateInfo(Long id, String name) { EntityManager em = entityManagerFactory.createEntityManager(); EntityTransaction tx = em.getTransaction(); // 1. 트랜잭션 시작 tx.begin(); // 2.User 엔티티를 조회 & User 스냅샷 생성 User user = em.find(User.class, id); // 3.User 엔티티의 name을 변경 user.setName(name); // 4. 트랜잭션 // 5.User 스냅샷과 최종 user의 내용을 비교해서 Dirty를 Checking 해서 Update Query 발생 tx.commit(); } }
Java
복사

더티 체킹이 발생하면 어떤 절차가 진행될까요?

위 1, 2번에서 더티 체킹이 일어나면 다음과 같은 Query 문이 발생한 것을 볼 수 있습니다.
분명 user의 업데이트에 대한 메서드를 호출하지 않았음에도 불구하고 Query가 발생시킵니다.(더티 체킹)
그리고 트랜잭션 커밋 이후, 트랜잭션이 끝나는 시점에 모든 엔티티에 대한 정보를 DB에 반영합니다.
더티 체킹시, 발생하는 Query 예시) Hibernate: update user set name=? where id=?
SQL
복사

그럼 트랜잭션이 아닌 상태에서 엔티티 내용이 변경되면 어떻게 될까요?

Service Layer 안, 동일한 코드 메서드에 @Transactional를 1) 사용한 경우, 2) 사용하지 않은 경우를 예시로 들겠습니다.
코드는 1, 2)가 다음과 같이 동일합니다.
public void updateInfo() { User user = userRepository.findById(2L) .orElseThrow(() -> new ErrorCodeException(ErrorType.USER_IS_NOT_EXISTING)); user.setEmail("hello@gmail.com"); System.out.println(userRepository.existsByEmail("hello@gmail.com")); }
Java
복사
위 코드로 어떻게 코드가 작동하는지 순서대로 보여드리겠습니다.
@Transactional를 사용한 경우
1. Table: User에서 PK id가 2인 user 엔티티 객체 조회
(Dirty Checking 발동)
2번 과정에서 수정되었기에 true 출력
@Transactional를 사용하지 않은 경우
1. Table: User에서 PK id가 2인 user 엔티티 객체 조회
2번 과정에서 수정되지 못해서
false 출력

Transaction을 사용하지 않아서 반영되지 못한 내용을 반영하고 싶은 경우

원하는 시점에 save, saveAndFlush를 사용합니다.
public void updateInfo() { User user = userRepository.findById(2L) .orElseThrow(() -> new ErrorCodeException(ErrorType.USER_IS_NOT_EXISTING)); user.setEmail("hello@gmail.com"); userRepository.saveAndFlush(user); System.out.println(userRepository.existsByEmail("hello@gmail.com")); } 1. Table: User에서 PK id가 2인 user 엔티티 객체 조회 2. user의 email을 hello@gmail.com로 수정 3. hello@gmail.com를 email로 가진 user 엔티티 유무 확인, 이번엔 userRepository.saveAndFlush(user);를 통해서 반영이 되어 true 출력
Java
복사
이상으로 JPA의 더티 체킹(Dirty Checking)에 대해서 설명을 마치겠습니다~!