코드를 보여주지 않는, 개념을 탐구하는 글이다. 김영한의 "자바 ORM 표준 JPA 프로그래밍" 책을 기반으로 공부했으나, 좀 더 정확한 개념 파악을 위해 공식 명세, 문서를 주로 찾아보고 정리했다.
JPA의 명세 내용을 기준으로 작성했고, 하이버네이트의 구현을 바탕으로 설명된 내용이 있으므로 참고하길 바란다.
엔티티 매니저는 엔티티를 저장하고, 수정하고, 삭제하고, 조회하는 등 엔티티와 관련된 모든 일을 처리한다. 엔티티를 관리하는 가상의 데이터베이스로 생각하면 편하다. 엔티티 매니저는 영속성 컨텍스트와 1대1 또는 1대N 관계를 가진다.
JPA 2.1 명세 문서의 '7.1 Persistence Context'를 살펴보면, 엔티티 매니저의 종류를 컨테이너 관리형와 애플리케이션 관리형으로 나눈다.
요약하자면, Java EE 환경에서는 한 영속성 컨텍스트가 여러 엔티티 매니저에 포함되는 1:N 관계가 될 수 있다. 그러나 Java SE 및 Java EE ACC에서는 1:1 관계만 허용된다.
JPA 명세에 따르면, 영속성 컨텍스트는 영속성 엔티티 인스턴스들의 모음이다.[1] 그리고 엔티티의 생명주기를 관리한다. 쉽게 말해, 영속성 컨텍스트는 엔티티의 CRUD를 담당하면서 저장했거나 불러온 엔티티를 기억하는 1차 캐시 역할을 한다.
엔티티 매니저와 영속성 컨텍스트
컨텍스트는 문맥이라는 뜻이다. 우리가 사람들이랑 대화를 할 때 한번 나온 얘기를 기억하고 다시 묻지 않는다. 이처럼 영속성 컨텍스트는 엔티티를 기억하는 것이라고 이해하면 편하다. 이때 영속성 컨텍스트는 1차 캐시로써 역할한다.
엔티티를 기억하므로 영속성 컨텍스트가 얻는 장점이 있다.
엔티티 생성/수정/삭제를 위해선 트랜잭션을 시작해야한다. 애플리케이션은 엔티티 매니저에게 트랜잭션 시작과 엔티티 상태 변경(persist, remove 등)을 차례로 요청한다. 그러나 엔티티 매니저는 엔티티의 상태 변경사항을 데이터베이스에 바로 반영하지 않고, 영속성 컨텍스트에 따로 저장해둔다. 엔티티 매니저에 커밋을 요청해야 변경사항이 한꺼번에 SQL로 변환되어 데이터베이스에 보내지고 커밋된다.
이렇게 영속성 컨텍스트에 쌓인 엔티티 상태 변경사항을 데이터베이스와 동기화하는 절차를 플러시(flush)라고 한다.[2] 이런 전략은 쓰기 지연(write-behind)라고 하고, 이때 영속성 컨텍스트는 엔티티 상태 변경사항을 저장하는 트랜잭션 내 쓰기 지연 캐시로써 역할한다.[3] 플러시가 일어나는 조건은 위에 언급된 트랜잭션 커밋 외에도 여러 경우가 있다. 이는 엔티티의 생명주기를 다루면서 언급하겠다.
왜 플러시를 사용해 데이터베이스 쓰기를 지연시킬까? 물리적 데이터베이스 트랜잭션은 가능하면 할수록 최대한 짧아야한다. 긴 트랜잭션은 락 경합을 발생시켜 대용량 요청 처리에 불리하다.[4]
영속성 컨텍스트는 단순히 논리적 개념이다. 실존하지 않는다. 이 말이 헷갈릴 수도 있는데, 쉽게 비유하자면 정치이념과 사랑을 예로 들 수 있다. 보수주의, 진보주의, 사회주의, 공산주의 등 정치이념은 실존하지 않는다. 그러나 이는 정치인, 정당, 민중으로 표현될 수 있는 개념이다. 사랑은 실존하지 않는다. 그러나 언어적 표현, 육체적 표현 등으로 표현될 수 있는 개념이다.
실제로 JPA의 엔티티 매니저를 상속한 하이버네이트의 Session
인터페이스 설명을 보면, 이렇게 적혀져 있다.
영속성 컨텍스트의 "개념을 나타낸다"라는 표현을 사용한다.
Java 애플리케이션과 하이버네이트 사이의 주요 런타임 인터페이스다. 논리적 트랜잭션과 연관된 엔티티 인스턴스 집합인 영속성 컨텍스트의 개념을 나타낸다.
정리하자면, 트랜잭션 내에서 사용되는 엔티티를 기억하고 그 엔티티의 생명주기를 관리한다면 그건 뭐든 간에 영속성 컨텍스트라고 말할 수 있다. 그리고 엔티티 매니저는 영속성 컨텍스트를 사용할 수 있는 메서드를 제공하는 인터페이스이다.
영속성 컨텍스트를 처음에 접했을 땐, 눈에 보이지 않는 논리적 개념에 가깝다길래 뭔 소린가 했다. 개념이 꼭 실존해야한다고 잠깐 착각했던 것 같다. 나를 설득하면서 남을 설득할만한 예를 생각해보니, 실존하지 않는 개념도 있었구나 싶었다.
Hibernate 클래스 중 PersistenceContext
인터페이스와 그 구현체 StatefulPersistenceContext
는 영속성 컨텍스트의 구현체 일까?
내 생각엔 아닐 것 같다. 영속성 컨텍스트는 영속성 엔티티 인스턴스의 모음이자 1차 캐시와 트랜잭션 내 쓰기 지연 캐시로써 역할을 한다.
그러나 위 인터페이스는 1차 캐시 역할만 맡고 있다. 따라서 아니라고 생각한다.
위에서 언급한 Session
의 구현체 SessionImpl
의 코드를 보자.
1차 캐시인 PersistenceContext
뿐만 아니라, 쓰기 지연 캐시인 ActionQueue
도 가지고 있고, DB 커넥션도 가지고 있다.
이를 영속성 컨텍스트로 보는게 더 정확하다고 본다.
다른 의견이 있거나 더 잘 아시는 분은 댓글 남겨주면 감사하겠다.
엔티티는 영속성 컨텍스트에 의해 생명주기가 관리된다. 생명주기는 4가지 상태가 존재한다.[5]
즉, 영속성 컨텍스트가 기억하고 있는지 없는지로 나눌 수 있다.
엔티티들의 생명주기를 관리하기 위해서는 엔티티 매니저가 제공하는 연산(메서드)을 사용한다. 어떤 연산을 사용해 각 상태로 전환할 수 있는지는 아래 그림을 참고하자.
엔티티 생명주기
이 그림으로 알 수 있듯이, 영속성 컨텍스트와 관련 있는 영속, 삭제 상태는 플러시를 거쳐 데이터베이스와 동기화가 되는 대상이다.
생명주기의 설명은 여기서 이어 설명하겠다.