영속성 컨텍스트
엔티티를 영구 저장하는 환경 이라는 뜻
em.persist(obj) - 엔티티를 영속성 컨텍스트에 저장한다는 뜻으로 단순 DB 저장 로직과는 다르다.
논리적인 개념
엔티티 매니저를 통해서 접근
엔티티의 생명주기
비영속 (new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
단순히 생성된 객체등을 의미
영속 (managed)
영속성 컨텍스트에 관리되는 상태
em.persist(obj) => 이후 해당 obj 는 영속성 컨텍스트에 의해 관리됨
준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
삭제 (removed)
삭제된 상태
준영속 vs 비영속
비영속은 한번도 영속성 컨텍스트에 저장된 적 없는 객체, 준영속은 저장되었다가 detach, clear, close 등으로 인해 영속성 컨텍스트에서 지워진 객체이다.
비영속 객체는 PK 를 갖지 않지만, 준영속 객체는 PK 를 갖는다. (영속성 컨텍스트에 저장되기 위해서는 PK 가 필요하므로)
추가.
detach 는 하나의 객체를 준영속 상태로 만드는 메서드
clear 는 해당 EntityManager 가 관리하는 영속성 컨텍스트를 초기화하는 것
close 는 해당 EntityManager 의 영속성 컨텍스트를 닫고 (더이상 사용할 수 없음) 이 외에 데이터베이스 연결, 캐시 등의 리소스가 모두 해제됨
영속성 컨텍스트의 이점
1차 캐시
em.persist(obj) 할 시, 해당 객체의 pk : obj 형태로 영속성 컨텍스트에 저장이 되어 이후 em.find(obj) 를 하면, DB에서 조회하는 것이 아니라 영속성 컨텍스트에서 바로 반환할 수 있음.
그러나, 트랜잭션 단위로 영속성 컨텍스트가 존재하므로 큰 이점을 보기는 어려움
영속 엔티티의 동일성 보장
같은 pk 로 em.find 실행 시, 같은 값을 갖는 객체를 매번 생성하는 것이 아니라, 영속성 컨텍스트에 저장된 객체의 참조값을 반환한다. 따라서, == 비교 시, true 가 반환된다.
트랜잭션을 지원하는 쓰기 지연
em.persist 로 새로운 객체를 생성할 때, 쓰기 지연 SQL 저장소에 INSERT 쿼리를 모아놨다가 트랜잭션 커밋 시, 한번에 쿼리를 날린다.
jdbc batch size 설정을 통해 모아놓을 쿼리의 개수를 정할 수 있다.
변경 감지
영속성 컨텍스트에 저장된 객체의 내부 값을 변경할 시, 변경을 감지하여 이후 커밋 시 알아서 UPDATE 쿼리를 날린다.
처음 영속성 컨텍스트에 객체가 저장되는 시점에 객체의 스냅샷을 같이 저장하여 커밋 시점에 스냅샷과 저장된 객체가 다르다면 UPDATE, DELETE 를 수행한다.
추가.
set 메서드를 통해 여러번 수정하면? => 최초 스냅샷과 비교를 통해 커밋 시점에 쿼리가 결정되므로 몇번 값이 변경되었든 커밋 시점의 엔티티와 스냅샷을 비교하므로 한번만 UPDATE 쿼리가 생성된다.
플러시
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
변경 감지
수정된 엔티티의 UPDATE 쿼리를 쓰기 지연 SQL 저장소에 등록
쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송
em.flush(), trx.commit(), em.createQuery() 메서드 실행 시 플러시가 일어난다.
추가.
flush 는 말 그대로 쓰기 지연 SQL 저장소의 쿼리들을 DB 에 반영하는 것이다. flush() 메서드는 플러시 동작을 위해 스냅샷과 비교하여 쿼리를 쓰기 지연 SQL 저장소에 저장하고 스냅샷을 변경하는 등 여러 작업을 수행한다.
flush() 메서드 실행 시 스냅샷이 현재 엔티티상태로 초기화된다.
JPQL 쿼리 실행시 왜 플러시가 일어날까? => JPQL 은 DB 에 쿼리를 날려 직접 데이터를 가져오는 방식이므로 영속성 컨텍스트에 저장이 되어있는지 확인을 하지 않는다. 따라서, 영속성 컨텍스트에만 반영이 되어있는 변경 사항을 DB에는 반영하지 못한 상태로 쿼리를 날려 잘못된 데이터를 받을 수 있으므로 자동으로 수행전에 플러시를 한다.
em.setFlushMode(mode) 를 통해 플러시를 언제할 지 설정할 수 있다.