
안녕하세요🐱
오늘은 JPA의 중요한 컨셉 중 하나인 Entity Manager와 Persistence Context에 대해 알아보겠습니다!
영속성 컨텍스트라고 불리는 Persistence Context는 Spring boot를 공부하면서 자주 언급되던 내용이었지만, 개념 정리가 한 번 필요했기 때문에 이번 기회를 통해 개념과 기본적인 특징에 대해서 공부해보겠습니다.
Entity는 Spring boot를 공부하면서 여러 번 접해 본 개념인데요, 이는 바로 데이터베이스의 테이블과 매핑되는 객체를 의미합니다.
Entity는 본질적으로는 자바 객체이므로 일반 객체와 다르지 않습니다.
하지만 데이터베이스의 테이블과 직접 연결된다는 아주 특별한 특징이 있어 이렇게 구분지어 부릅니다.
즉, Entity는 객체이긴 하지만 데이터베이스에 영향을 미치는 쿼리를 실행하는 객체인 것이죠.
Entity Manager는 그런 Entity들을 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할을 합니다.
그리고 이런 Entity Manager를 만드는 곳이 바로 Entity Manager Factory입니다.
예를 들어, 회원 2명이 동시에 회원 가입을 하려는 경우..!
Entity Manager는 다음과 같이 업무를 처리합니다.
회원 1의 요청에 대해서 가입 처리를 할 Entity Manager를 Entity Factory가 생성하면, 이를 통해 가입 처리해 데이터베이스에 회원 정보를 저장합니다.
회원 2도 마찬가지입니다.
그리고 회원 1, 2를 위해 생성된 Entity Manager는 필요한 시점에 데이터베이스와 연결한 뒤에 쿼리합니다.

그렇다면 Spring boot에서도 직접 Entity Manager Factory를 만들어서 관리할까요?
사실 그렇지 않습니다!
Spring boot는 내부에서 Entity Manager Factory를 하나만 생성해서 관리하고, @Persistence Context또는 @Autowired 어노테이션을 사용해서 Entity Manager를 사용합니다.
@PersistenceCotext
EntityManager em;
그리고 Sprint boot는 기본적으로 Bean을 하나만 생성해서 공유하므로, 동시성 문제가 발생할 수 있습니다.
그래서 실제로는 Entity Manager가 아닌 실제 Entity Manager와 연결하는 Proxy Entity Manager를 사용합니다. 필요할 때 데이터베이스 트랜잭션과 관련된 실제 Entity Manager를 호출하는 것이죠!
쉽게 말해, Entity Manager는 Spring Data JPA에서 관리하므로
직접 생성하거나 관리할 필요가 거의 없다고 보시면 됩니다!
Entity Manager는 Entity를 Persistence Context에 저장한다는 특징이 있습니다.
Persistence Context(영속성 컨텍스트)란, JPA의 중요한 특징 중 하나로, Entity를 관리하는 가상의 공간입니다.
이 Persistence Context가 있기 때문에 데이터베이스에서 데이터를 효과적으로 가져올 수 있고, Entity를 편하게 사용할 수 있는 것입니다.
Persistence Context는 내부에 1차 캐시를 가지고 있습니다. 이 때 캐시의 키는 Entity의 @Id 어노테이션이 달린 기본키 역할을 하는 식별자이며, 값은 Entity입니다.
Entity를 조회하면 1차 캐시에서 데이터를 조회하고 값이 있으면 반환합니다. 만약 값이 없으면 데이터베이스에서 조회해 1차 캐시에 저장한 다음 반환합니다.
이를 통해 캐시된 데이터를 조회할 때에는 데이터베이스를 거치지 않아도 되므로, 매우 빠르게 데이터를 조회할 수 있습니다.
쓰기 지연(transactional write-behind)은 트랜잭션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 쿼리를 모았다가 트랜잭션을 커밋하면 모았던 쿼리를 한 번에 실행하는 것을 의미합니다.
예를 들어 데이터 추가 쿼리가 3개라면 Persistence Context는 트랜잭션을 커밋하는 시점에 3개의 쿼리를 한꺼번에 전송합니다.
이를 통해 적당한 묶음으로 쿼리를 요청할 수 있어, 데이터베이스 시스템의 부담을 줄일 수 있습니다.
트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 Entity의 값과 현재 Entity의 값을 비교해서 변경된 값이 있다면, 변경 사항을 감지해 변경된 값을 데이터베이스에 자동으로 반영합니다.
이를 통해 쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고, 데이터베이스 시스템의 부담을 줄일 수 있습니다.
지연 로딩(lazy loading)은 쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미합니다.

Entity는 4가지 상태를 가집니다.
Persistence Context가 관리하고 있지 않은 detached,
Persistence Context가 관리하는 managed,
Persistence Context와 전혀 관계가 없는 transient, removed로 나눠집니다.
비영속 상태는 Entity 객체를 생성했지만, 아직 Persistence Context에 저장하지 않은 상태입니다.
Member member = new Member(); // 현재 member는 비영속 상태이다.
영속 상태는 Entity Manager를 통해서 Persistence Context에 저장된 상태입니다.
em.persist(member); // persist를 통해 영속 상태가 된다.
Persistence Context가 관리하던 영속 상태의 Entity를 더 이상 관리하지 않으면 준영속 상태가 됩니다.
특정 Entity를 준영속 상태로 만들기 위해서는 em.detach()를 호출하면 됩니다.
// 엔티티를 영속성 컨텍스트에서 분리해 준영속 상태로 만든다.
em.detach(member);
// 영속성 콘텍스트를 비워도 관리되던 엔티티는 준영속 상태가 된다.
em.claer();
// 영속성 콘텍스트를 종료해도 관리되던 엔티티는 준영속 상태가 된다.
em.close();
준영속 상태일 때는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 Persistence Context가 제공하는 어떠한 기능도 동작하지 않습니다.
Entity를 Persistence Context와 데이터베이스에서 삭제합니다.
em.remove(member);
📚Reference
JPA 영속성 컨텍스트란?
스프링 데이터 JPA, 5분만에 알아보기 - 골든래빗