[JPA] 영속성 컨텍스트의 이점 ( + DirtyChecking )
영속성 컨텍스트의 동작원리를 간단히 설명하면,
엔티티(member)는 영속화(persist)되어 1차 캐시에 저장된다. 그리고 쓰기지연SQL저장소에는 INSERT문이 생성되어 1차캐시에 등록된 데이터를 DB 테이블에 추가할 준비를 한다. 그리고 flush 명령이 내려오면 쓰기지연SQL저장소에 저장된 쿼리들이 실행되면서 1차캐시와 DB가 동기화된다. 마무리로 커밋(commit)까지 완료되면 완전히 1차캐시 내용이 DB에 반영된다.
위 작업은 모두 단일한 트랜잭션 내에서 일어난다. 고로, 영속성 컨텍스트란 트랜잭션 내에서 일어나는 작업을 모두 기록하는 공간이다. 그리고 모든 작업은 언제든 ROLLBACK 될 수 있다.
영속성 컨텍스트를 사용하면 어떤 점이 좋을까?
☑︎ 1차캐시에서 조회
☑︎ 영속 엔티티 동일성 보장
☑︎ 쓰기지연
☑︎ 변경감지
◎ 1차캐시에서 조회
조회는 SELECT문이다.
EntityManger가 조회(find)를 요청하면 1차캐시에 원하는 엔티티가 없는 경우 DB에 SELECT문을 실행하여 조회한다. 그리고 결과를 1차캐시에 저장하고 엔티티 객체를 생성하여 반환(return)한다. 이후 단일 트랜잭션에서 같은 엔티티를 조회할 경우 1차캐시에서 바로 엔티티 조회가 가능하다.
◎ 영속 엔티티 동일성 보장
1차 캐시는 플러쉬(flush)가 수행되지 않는 이상 DB와 동기화 되지 않는다. 그러므로 1차캐시에 존재하는 엔티티는 언제나 늘 동일한 엔티티이다. 이것의 장점은 트랜잭션 격리 수준을 REPEATABLE READ 등급으로 유지함에 있다.
여러 트랜잭션이 동시에 하나의 DB에 작업하다보면 문제가 발생한다.
트랜잭션A가 시작되었다.
트랜잭션A는 컬럼1을 20으로 읽었다. ( READ )
트랜잭션B가 시작되었다.
트랜잭션B는 컬럼1을 40으로 바꾸었다. ( WRITE )
트랜잭션B가 커밋했다. ( COMMIT )
트랜잭션A가 다시 컬럼1을 읽었다. ( READ )
여기서 트랜잭션A는 컬럼1을 20으로 읽어야 할까? 아니면 40으로 읽어야 할까? 트랜잭션 격리 수준에 따라 답은 달라진다. REPEATABLE READ 등급이면 답은 20이다. 단일한 트랜잭션 내의 동일한 컬럼의 데이터를 일관성있게 유지해야한다. 처음에 20이면 나중도 20이어야 한다.
REPEATABLE READ 등급은 중간에 다른 트랜잭션이 데이터를 변경하고 커밋하여도 백업영역에 저장된 커밋전 데이터를 읽어들여 데이터 일관성을 유지한다. 영속성 컨텍스트는 처음 조회(SELECT) 했을때 1차캐시에 저장되었던 값을 조회한다. flush로 DB와 동기화되지 않는 이상 데이터 일관성이 계속 유지된다.
◎ 쓰기지연
영속성컨텍스트는 쓰기지연SQL 저장소를 가진다. SQL문이 생성되어도 바로 실행되지 않고 저장된다. 마치 저장소는 버퍼와 같다. 버퍼가 존재하면 잦은 I/O 발생을 막을 수 있다. 그리고 JPA는 원하는 시점에 개발자가 SQL문을 실행할 수 있도록, 플러쉬(flush) 기능을 제공한다.
플러시가 호출되는 경우는 3가지이다.
① entitymanager.flush() : 개발자가 직접호출
② tx.commit() : 자동호출
③ JPQL 쿼리 실행 : 자동호출
커밋하기전에 저장소에 있는 SQL문들을 플러시하여 실행시킨 후, DB에 반영한다. JPQL은 JPA 맞춤 SQL 쿼리문이다. JPQL은 EntityManager가 실행하는 쿼리로 파싱되어 SQL문으로 변환된다. JPQL은 쓰기지연SQL저장소에 저장되지 않고 바로 DB에 실행된다. 그러므로 이전에 쓰기지연SQL저장소에 저장했던 SQL문들을 먼저 flush해야 데이터에 문제가 생기지 않는다.
◎ 변경감지(Dirty Checking)
영속성컨텍스트는 1차캐시에 저장된 엔티티의 처음상태를 스냅샷으로 만들어 저장한다. 스냅샷을 '순수'한 상태라고 생각해보자. 개발자가 find()로 엔티티를 조회했다. 엔티티 객체는 생명주기가 관리되는 엔티티로 단일 트랜잭션 안에서는 늘 동일한 엔티티이다. 그런 엔티티에 개발자가 setter 함수로 접근하여 필드 데이터를 변경했다고 가정하자.
그럼 엔티티는 순수한 스냅샷과 동일했던 과거와 달리, 더럽혀졌다.(Dirty)
영속성 컨텍스트는 초기상태를 저장한 스냅샷과 엔티티를 비교하여 얼마나 더러워졌는지를 체크하는데, 이를 DirtyCheking 즉, 변경감지라 부른다. 변경이 감지되면 영속성컨텍스트는 자동으로 UPDATE문을 생성하여 쓰기지연SQL저장소에 저장한다. 개발자는 단순히 필드데이터만 변경하였는데 자동으로 UPDATE문까지 생성되는 것이다. 변경감지기능이 있어 개발자는 손쉬운 개발이 가능해진다.
public class JpaMain {
public static void main(String[] args) {
//EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("h2");
//EntityManager 생성
EntityManager entityManager = emf.createEntityManager();
//EntityTransaction 생성
EntityTransaction tx = entityManager.getTransaction();
tx.begin(); //트랜잭션 시작
try{
Account accountA = new Account("사쿠라",20000); // 엔티티 생성
Account accountB = new Account("김채원", 30000); // 엔티티 생성
entityManager.persist(accountA); // 계좌 영속화 [ INSERT 문 ]
entityManager.persist(accountB); // 계좌 영속화 [ INSERT 문 ]
accountA.withdraw(5000); // 출금 [변경감지] [ UPDATE 문 ]
accountB.deposit(5000); // 입금 [변경감지] [ UPDATE 문 ]
tx.commit(); // INSERT문 2개, UPDATE 문 2개 DB 반영
}catch (Exception e){
tx.rollback(); // 트랜잭션 롤백
}finally {
entityManager.close();
}
}
}
withdraw 메소드와 deposit 메소드는 계좌 엔티티의 amount 필드데이터를 변경한다. 필드데이터만 단순히 변경되었을 뿐인데, tx.commit()을 하면, 자동으로 UPDATE문이 실행되어 변경된 데이터를 DB에 반영한다.
이번에 정리한 4가지 특성이 JPA가 영속성 컨텍스트를 유지하는 이유이다.
☑︎ 1차캐시에서 조회
☑︎ 영속 엔티티 동일성 보장
☑︎ 쓰기지연
☑︎ 변경감지
참고자료