[자바 ORM 표준 JPA 프로그래밍 - 기본편] 섹션 04. 영속성 관리 - 내부 동작 방식
1. 영속성 컨텍스트 1
JPA에서 가장 중요한 것을 2가지 뽑는다면 객체와 관계형 DB 매핑하기와 영속성 컨텍스트이다.
지난 시간에도 잠깐 나왔는데 엔티티 매니저 팩토리는 클라이언트의 요청마다 엔티티 매니저를 생성하며 엔티티 매니저가 DB 커넥션을 사용해서 DB에 쿼리를 보내는 구조다.
영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻으로 persist 메서드를 통해 엔티티를 넣으면 해당 엔티티는 DB에 저장되는 것이 아니라 이 영속성 컨텍스트에 저장된다. 영속성 컨텍스트는 논리적인 개념으로 엔티티 매니저를 통해 접근할 수 있으며 J2SE 환경에서는 엔티티 매니저와 1:1로, J2EE 나 스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1 관계를 가진다.
엔티티는 아래와 같은 4가지 생명주기를 가진다.
- 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속(managed): 영속성 컨텍스트에 관리되는 상태
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed): 삭제된 상태
아래와 같이 객체를 생성만 한 경우 해당 객체는 영속성 컨텍스트와 아직 아무 관계가 없으며 이런 상태를 비영속 상태라고 한다.
생성한 객체를 persist 메서드로 엔티티 매니저에 저장하면 해당 객체는 영속성 컨텍스트로 관리되며 이를 영속 상태라고 한다.
persist 전후로 BEFORE와 AFTER를 출력하는 코드를 넣고 실행을 하면 BEFORE와 AFTER가 출력된 후 INSERT 쿼리가 나가는 것을 볼 수 있다. 이를 통해 persist 메서드를 호출한 시점에 DB에 삽입이 되는 것이 아님을 볼 수 있다. 실제 쿼리는 트랜잭션을 커밋하는 시점에 나간다.
2. 영속성 컨텍스트 2
영속성 컨텍스트는 아래와 같은 5가지 이점이 있다.
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
영속성 컨텍스트는 내부에 1차 캐시가 있다.
영속성 컨텍스트로 관리되는 엔티티를 조회할 경우 DB에 SELECT 쿼리를 날리지 않고 영속성 컨텍스트의 1차 캐시에서 조회한다.
1차 캐시에 해당 객체가 없으면 DB에서 조회 후 이를 1차 캐시에 저장하고 1차 캐시에 저장된 객체를 반환한다.
영속성 컨텍스트로 관리되는 객체를 find 메서드로 조회한 경우 SELECT 쿼리가 나가지 않는 것을 볼 수 있다.
현재 DB에 ID가 101인 레코드가 존재하는 상황에서 동일한 ID로 두 번 조회할 경우 SELECT 쿼리가 한번만 나가는 것을 볼 수 있다. 1차 캐시를 통한 성능 향상 효과는 일반적으로 크지는 않지만 비즈니스 로직이 복잡하면 도움이 될 수도 있다.
==을 통한 참조값 비교에서 동일한 객체임을 볼 수 있다. 영속 엔티티의 동일성을 보장한다는 점 덕분에 REPEATABLE READ 등급의 트랜잭션 격리 수준을 애플리케이션 차원에서 제공할 수 있다.
JPA는 쓰기 지연도 제공하는데 persist로 memberA를 저장하고 이후 memberB를 저장할 경우 영속성 컨텍스트 내부의 쓰기 지연 SQL 저장소에 INSERT SQL을 생성해서 쌓아둔다.
트랜잭션을 커밋하는 시점에 쌓인 SQL이 flush가 되면서 커밋된다.
먼저 편의를 위해 Member에 생성자를 만들어줬다. JPA를 리플렉션을 활용해서 동적으로 객체를 생성하는데 이 때문에 기본 생성자도 만들어줘야 한다.
두 Member 객체를 생성해서 persist한 경우 출력 결과를 보면 막대가 먼저 출력되고 이후 INSERT 쿼리가 나가는 것을 볼 수 있다.
쿼리를 모았다가 한번에 보내기 때문에 배치를 활용한 성능 향상도 해볼 수 있다.
현재 DB에 저장된 데이터는 아래와 같다.
ID가 150인 멤버의 이름을 변경한 경우 출력 결과를 보면 먼저 1차 캐시에 없는 객체이므로 SELECT 쿼리가 나가고 이후 막대가 출력된 후 UPDATE 쿼리가 나가는 것을 볼 수 있다.
DB에서 조회해보면 잘 바뀐 것을 볼 수 있다.
JPA는 변경 감지를 지원하는데 1차 캐시의 엔티티와 스냅샷을 비교한다. 스냅샷은 처음 1차 캐시에 적재될 때의 스냅샷이다. 비교에서 변경 사항이 있으면 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 생성한다.
3. 플러시
플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것이다. 플러시가 발생하면 변경 감지가 일어나서 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록하고, 쓰기 지연 SQL 저장소에 쌓인 등록, 수정, 삭제 쿼리 등을 DB에 전송한다.
영속성 컨텍스트를 플러시하는 방법은 flush 메서드를 직접 호출할 수 있고, 트랜잭션이 커밋되거나 JPQL 쿼리를 실행하면 플러시가 자동으로 호출된다.
플러시는 영속성 컨텍스트를 비우지 않으며 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 과정이라 보면 된다.
새로운 멤버를 생성해서 영속성 컨텍스트에 저장 후 flush를 호출한 경우 INSERT 쿼리가 막대 출력보다 먼저 발생한 것을 볼 수 있다.
4. 준영속 상태
준영속 상태는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 상태로 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
준영속 상태로 만드는 방법은 detach 메서드를 통해 특정 엔티티만 준영속 상태로 전환할 수도 있고, clear 메서드로 영속성 컨텍스트를 완전히 초기화하는 방법을 사용할 수도 있고, close 메서드로 영속성 컨텍스트를 종료하는 방법도 있다.
find로 조회했으니 영속성 컨텍스트에 영속된 상태인데 detach로 준영속 상태로 만들어서 SELECT 쿼리는 나가지만 UPDATE 쿼리는 나가지 않는 것을 볼 수 있다.
clear 메서드로 영속성 컨텍스트를 초기화하니 동일한 멤버를 조회해도 SELECT 쿼리가 두 번 나가는 것을 볼 수 있다.






















