JPA Dirty Checking

신민철·2023년 3월 30일
1
post-thumbnail

더티 체킹(Dirty Checking)이란?

  • JPA는 엔티티 매니저가 엔티티를 저장/조회/수정/삭제를 한다.
  • 그런데 엔티티 매니저의 메서드를 찾아보면, 저장(persist)/조회(find)/수정(?)/삭제(delete)로 수정에 해당하는 메서드가 없다.
  • 대신에 수정(?)에 해당하는 더티 체킹(Dirty Checking)을 지원한다.
  • 더티 체킹은 Transaction 안에서 엔티티의 변경이 일어나면, 변경 내용을 자동으로 데이터베이스에 반영하는 JPA 특징이다.
  • 데이터베이스에 변경 데이터를 저장하는 시점 : 1) Transaction commit 시점 2) EntityManager flush 시점 3) JPQL 사용 시점
  • 또한, 영속성 컨택스트(Persistence Context) 안에 있는 엔티티를 대상으로 더티 체킹이 일어난다.
  • 여기서 Dirty란 "엔티티 데이터의 변경된 부분"이라고 해석될 수 있다. 즉, 변경된 부분을 체크해서 DB에 반영한다는 뜻으로 해석한다.

JPA 영속성 컨텍스트

                                    JPA 영속성 컨텍스트

더티 체킹이 일어나는 환경은 아래 두 가지 조건이 충족되어야 한다.

  • 영속 상태(Managed) 안에 있는 엔티티인 경우
  • Transaction 안에서 엔티티를 변경하는 경우 → AOP 때문에 영속 상태로 꺼내오기 때문에 가능한 것이다!

Transaction은 두 가지 방식으로 사용할 수 있다.

  1. Service Layer에서 @Transactional을 사용하는 경우
  2. EntityTransaction을 이용해서 트랜잭션을 범위를 지정하고 사용하는 경우

여기서 위 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);
	}
}

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

@Service
public class ExampleService {
     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();
     }
}

더티 체킹이 발생하면 어떤 일이 일어날까?

위 1번/2번에서 더티 체킹이 일어나면 다음과 같은 Query 문이 발생한 것을 볼 수 있다.

분명 user의 업데이트에 대한 메서드를 호출하지 않았음에도 불구하고 Query가 발생시킨다.(더티 체킹)

그리고 트랜잭션 커밋 이후, 트랜잭션이 끝나는 시점에 모든 엔티티에 대한 정보를 DB에 반영한다.

더티 체킹시, 발생하는 Query

Hibernate:
update
	**user
set**
	name=?
**where**
	id=?

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

Service Layer 안, 동일한 코드 메서드에 @Transactional을 사용한 경우와 사용하지 않은 경우를 예시로 들겠습니다.

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"));
}

@Transactional을 사용한 경우

  1. Table: User에서 PK id가 2인 user 엔티티 객체 조회
  2. user의 email을 수정(Dirty Checking 발생)
  3. hello@gmail.com을 email로 가진 user 엔티티 유무 확인, 2번 과정에서 수정되었기에 true가 출력됨

@Transactional을 사용하지 않은 경우

  1. Table: User에서 PK id가 2인 엔티티 객체 조회
  2. user의 email을 수정
  3. hello@gmail.com을 email로 가진 user 엔티티 유무 확인
    4. 2번 과정에서 수정되지 못하여 false 출력

Transaction을 사용하지 않아서 반영되지 못한 내용을 반영하고 싶은 경우, 원하는 시점에 save 혹은 saveAndFlush를 사용하면 된다.

@Transactional을 사용하지 않은 경우의 반영하는 케이스

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 출력

참고

더티 체킹(Dirty Checking)이란?

  • JPA는 엔티티 매니저가 엔티티를 저장/조회/수정/삭제를 한다.
  • 그런데 엔티티 매니저의 메서드를 찾아보면, 저장(persist)/조회(find)/수정(?)/삭제(delete)로 수정에 해당하는 메서드가 없다.
  • 대신에 수정(?)에 해당하는 더티 체킹(Dirty Checking)을 지원한다.
  • 더티 체킹은 Transaction 안에서 엔티티의 변경이 일어나면, 변경 내용을 자동으로 데이터베이스에 반영하는 JPA 특징이다.
  • 데이터베이스에 변경 데이터를 저장하는 시점 : 1) Transaction commit 시점 2) EntityManager flush 시점 3) JPQL 사용 시점
  • 또한, 영속성 컨택스트(Persistence Context) 안에 있는 엔티티를 대상으로 더티 체킹이 일어난다.
  • 여기서 Dirty란 "엔티티 데이터의 변경된 부분"이라고 해석될 수 있다. 즉, 변경된 부분을 체크해서 DB에 반영한다는 뜻으로 해석한다.

                                             JPA 영속성 컨텍스트

                                      JPA 영속성 컨텍스트

더티 체킹이 일어나는 환경은 아래 두 가지 조건이 충족되어야 한다.

  • 영속 상태(Managed) 안에 있는 엔티티인 경우
  • Transaction 안에서 엔티티를 변경하는 경우 → AOP 때문에 영속 상태로 꺼내오기 때문에 가능한 것이다!

Transaction은 두 가지 방식으로 사용할 수 있다.

  1. Service Layer에서 @Transactional을 사용하는 경우
  2. EntityTransaction을 이용해서 트랜잭션을 범위를 지정하고 사용하는 경우

여기서 위 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);
	}
}

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

@Service
public class ExampleService {
     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();
     }
}

더티 체킹이 발생하면 어떤 일이 일어날까?

위 1번/2번에서 더티 체킹이 일어나면 다음과 같은 Query 문이 발생한 것을 볼 수 있다.

분명 user의 업데이트에 대한 메서드를 호출하지 않았음에도 불구하고 Query가 발생시킨다.(더티 체킹)

그리고 트랜잭션 커밋 이후, 트랜잭션이 끝나는 시점에 모든 엔티티에 대한 정보를 DB에 반영한다.

더티 체킹시, 발생하는 Query

Hibernate:
update
	**user
set**
	name=?
**where**
	id=?

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

Service Layer 안, 동일한 코드 메서드에 @Transactional을 사용한 경우와 사용하지 않은 경우를 예시로 들겠습니다.

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"));
}

@Transactional을 사용한 경우

  1. Table: User에서 PK id가 2인 user 엔티티 객체 조회
  2. user의 email을 수정(Dirty Checking 발생)
  3. hello@gmail.com을 email로 가진 user 엔티티 유무 확인, 2번 과정에서 수정되었기에 true가 출력됨

@Transactional을 사용하지 않은 경우

  1. Table: User에서 PK id가 2인 엔티티 객체 조회
  2. user의 email을 수정
  3. hello@gmail.com을 email로 가진 user 엔티티 유무 확인
    4. 2번 과정에서 수정되지 못하여 false 출력

Transaction을 사용하지 않아서 반영되지 못한 내용을 반영하고 싶은 경우, 원하는 시점에 save 혹은 saveAndFlush를 사용하면 된다.

@Transactional을 사용하지 않은 경우의 반영하는 케이스

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 출력

참고

더티 체킹 (Dirty Checking)이란?
[Spring JPA] Dirty Checking(더티체킹)이란?
[Spring Data JPA] 더티 체킹(Dirty Checking)

0개의 댓글