들어가며
이전 글에서 레이어드 아키텍처를 알아보면서 백엔드 시스템의 기본 구조를 이해할 수 있었다. 그다음 실습을 하기 위해 데이터베이스를 연동하면서 영속성 컨텍스트를 알게 되었고 존재 이유를 자세히 알고 싶어 이번 블로깅 주제로 잡았다.
영속성 컨텍스트(Persistence Context)
애플리케이션과 데이터베이스 사이에서 엔티티와 레코드의 괴리를 해소하는 기능과 객체를 보관하는 기능을 수행
다시 말해서 JPA가 데이터베이스와 직접 연결하지 않고 엔티티 객체를 관리하는 메모리 공간이다. JPA는 ORM의 한 종류로 객체 관계 매핑을 자바에서 표준으로 채택된 인터페이스의 모음이다.

위의 그림을 보면 데이터베이스에 접근하기 위한 세션이 생성되면 영속성 컨텍스트가 만들어져 영속 객체로 관리된다. 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 DB역할을 한다.
그렇다면 굳이 왜 애플리케이션과 DB 사이에서 또 다른 메모리 공간을 만들어서 관리를 하는 것인지 의문이 들었다. 영속성 컨텍스트가 필요한 이유를 하나씩 알아보자.
영속성 컨텍스트의 장점
- 1차 캐시
엔티티(객체)를 1차 캐시에 저장하기 때문에 동일한 엔티티를 여러 번 조회하더라도 DB에 중복해서 접근하지 않고 캐시에서 가져오므로 성능을 향상시킨다.

위 이미지를 통해 1차 캐시가 어떻게 활용되는지 흐름을 통해 알아보자.
먼저 member2라는 엔티티를 조회하려고 한다. JPA은 이 요청을 받고 영속성 컨텍스트의 1차 캐시를 확인한다. 이때, 1차 캐시는 key, value로 가지는 Map 형태이다. 위 이미지에서는 member2라는 엔티티가 1차 캐시에 존재하지 않았고 JPA는 DB로 쿼리를 날려 해당 데이터를 조회한다. 그다음 DB에서 조회된 member2 데이터를 기반으로 JPA는 member2 엔티티를 생성하고 즉시 영속성 컨텍스트의 1차 캐시에 저장한다. 이후에 member2라는 데이터를 조회한다면, 1차 캐시에 이미 정보가 저장되어있기 때문에 DB부하 없이 빠른 성능을 제공한다.
- 쓰기 지연
엔티티의 변경 사항을 즉시 데이터베이스에 반영하지 않고, 영속성 컨텍스트 내부(쓰기 지연 SQL 저장소)에 임시로 모아두었다가 특정 시점에 한 번에 DB에 동기화(Flush)하는 전략이다.

persist() 메소드를 통해 새로운 엔티티 memberB를 DB에 추가하려 한다. 먼저 1차 캐시에 저장 후 영속 상태가 되면 JPA는 즉시 DB로 쿼리를 날리지 않고 SQL 쿼리를 쓰기 지연 SQL 저장소에 임시로 보관한다. 그다음 트랜잭션이 커밋될 때 임시로 보관된 쿼리들을 한 번에 DB에 전송하여 DB에도 실제 데이터 변경이 반영된다.
- 동일성 보장
하나의 영속성 컨텍스트 내에서 같은 식별자(Primary Key)를 가진 엔티티는 항상 동일한 인스턴스로 관리된다는 원칙을 의미한다. 같은 식별자를 가진 엔티티를 여러 번 조회하더라도 매번 새로운 객체를 생성하지 않고 기존의 동일한 객체 인스턴스를 반환한다. 이는 1차 캐시를 설명할 때와 동일한 내용이므로 예시는 생략하겠다.
- 변경 감지 및 자동 반영
영속성 컨텍스트가 관리하는 영속 상태의 엔티티에 대해서, 트랜잭션이 커밋되는 시점에 엔티티의 변경된 내용을 자동으로 감지하여 데이터베이스에 UPDATE 쿼리를 날려주는 기능이다. 직접 엔티티의 필드 값을 변경하고 명시적으로 update 같은 메서드를 호출하지 않아도 JPA가 알아서 변경을 감지하고 DB에 반영한다.

트랜잭션이 커밋될 때 자동으로 flush 메서드가 호출된다. 이때 JPA는 영속성 컨텍스트 내의 모든 영속 상태 엔티티를 검사하여 각 엔티티의 현재 상태 즉, 1차 캐시의 엔티티 스냅샷을 비교한다. '스냅샷'이란 영속성 컨텍스트에 엔티티가 처음 로드되어 영속 상태가 될 때, 해당 엔티티의 초기 상태를 복사하여 별도로 저장해 둔 것이다. 스냅샷과 비교한 결과에서 변경된 부분이 감지되면 JPA는 해당 엔티티에 대한 UPDATE SQL 쿼리를 생성한다. 이렇게 스냅샷을 통한 변경 감지로 JPA가 영속 상태의 엔티티 변경을 자동으로 감지하고 DB에 반영해준다.
영속성 컨텍스트를 쓰는 이유를 예시와 함께 알아봤다. 이제 엔티티가 영속성 컨텍스트에서 상태가 어떻게 변하는지 알아보자.
Entity의 생명주기
엔티티의 생명주기는 크게 네 가지로 분류된다. 엔티티는 비영속, 영속, 준영속, 삭제의 여러 상태를 거치게 된다.
-
비영속(New)
엔티티 객체를 생성했지만, 아직 영속성 컨텍스트와 아무런 관련이 없는 상태이다. DB에도 저장되지도 않고, JPA의 관리를 받지도 않는다.
-
영속(Managed)
persist() 메서드를 호출 받고 엔티티 객체가 영속성 컨텍스트에 저장되어 JPA의 관리를 받는 상태이다. 1차 캐시에 저장되고, DB와의 동기화 준비가 완료된다. 이때, 위에서 다룬 1차 캐시, 쓰기 지연, 변경 감지, 동일성 보장이 적용된다.
-
준영속(Detached)
영속성 컨텍스트에 저장되었다가 분리된 상태이다. 영속성 컨텍스트가 더 이상 해당 엔티티를 관리하지 않고, JPA의 혜택을 받을 수 없는 상태이다. 하지만 데이터는 여전히 존재한다.
-
삭제(Removed)
엔티티가 영속성 컨텍스트에서 삭제되고, DB에서도 삭제되도록 예약된 상태이다.
위 이미지는 JPA 애플리케이션에서 엔티티가 생성부터 소멸까지 어떤 상태들을 거치며 진행되는지 보여준다. 그렇다면 엔티티의 상태를 변경하고 관리는 어떻게 이루어질까?
EntityManager
엔티티 메니저는 JPA에서 엔티티를 관리하고, DB와의 상호작용을 담당하는 가장 핵심적인 객체이다. 영속성 컨텍스트에 접근하고 그 안에서 엔티티를 관리하는 데 사용되는 인터페이스로도 볼 수 있다. 이제 엔티티 매니저의 역할을 들여다보자.
- persist(): 새로운 엔티티를 영속성 컨텍스트에 등록하고 관리한다.
- find(): 1차 캐시에 먼저 접근하여 엔티티 존재 여부를 확인한다. 캐시에 없다면 DB에서 데이터를 가져와 1차 캐시에 저장하여 스냅샷을 형성한다.
- detach(), clear(), close(): 영속성 건텍스트로부터 엔티티를 분리하여 준영속 상태로 만든다. JPA의 자동 관리를 중단 시킨다.
- merged(): 준영속 상태의 엔티티를 다시 영속성 컨텍스트의 관리 대상으로 되돌린다.
- flush(): 영속성 컨텍스트의 변경 사항을 실제 DB로 전달한다.
위과 같은 메서드들을 통해 엔티티의 생명주기를 관리하고, 영속성 컨텍스트의 핵심 기능을 실행하여 DB와 엔티티 간의 모든 데이터 동기화를 책임지는 JPA의 중앙 제어자 역할을 수행하게 된다.
참고자료