Persistence Context 란

Persistence providers like Hibernate make use of persistence context to manage the entity lifecycle in an application.

JPA (Java Persistence API)에서 Persistence Context는 엔티티 객체의 생명 주기를 관리하고 영속성을 제공하는 환경을 의미한다.

Persistence Context 의 주요 요소를 살펴보자.
한눈에 파악할 수 있도록 도식화를 해보았다! (보완이 필요할 수 있으니 참고만 하자)

Persistence Unit

엔티티 클래스 및 이들 클래스를 관리하는 데이터 소스 설정을 포함하는 구성 단위
쉽게 말해서, EntityManagerFactory 가 엔티티 관리를 위해 사용되는 일련의 설정을 의미한다.

A persistence unit defines a set of all entity classes that are managed by EntityManager instances in an application

persistence unit 은 persistence.xml 과 같은 파일에 정의한다.

<persistence>
    <persistence-unit name="OrderManagement">
        <description>This unit manages orders and customers.
            It does not rely on any vendor-specific features and can
            therefore be deployed to any persistence provider.
        </description>
        <jta-data-source>jdbc/MyOrderDB</jta-data-source>
        <jar-file>MyOrderApp.jar</jar-file>
        <class>com.widgets.Order</class>
        <class>com.widgets.Customer</class>
    </persistence-unit>
</persistence>

데이터 소스, 엔티티 클래스, JPA 공급자 등 여러 가지 설정이 포함된다.

Spring Boot 에서는 위와 같은 설정을 application.yml 파일에 작성한다.
Spring Boot 가 자동으로 persistence unit을 설정하여 EntityManagerFactory를 구성하므로, 별도의 persistence.xml 파일을 작성할 필요가 없다.

아래는 이번 미션에서 작성해준 application.yml 파일이다.

spring:
  datasource:
    url: jdbc:h2:mem:database
  h2:
    console:
      enabled: true
      path: /h2-console
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
    hibernate:
      ddl-auto: create
    defer-datasource-initialization: true

자세한 내용은 스프링 공식문서에도 나와있다.

PersistenceProvider

JPA 구현체이다. Hibernate, EclipseLink, OpenJPA 등이 있다.
PersistenceProvider 는 enum type 이다.

Enum PersistenceProvider
java.lang.Object
java.lang.Enum
org.springframework.data.jpa.provider.PersistenceProvider

내부 구현을 살짝 살펴보니, 아래와 같이 구성되어 있었다.

public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment {
HIBERNATE(Collections.singletonList("org.hibernate.engine.spi.SessionImplementor"), Collections.singletonList("org.hibernate.metamodel.model.domain.JpaMetamodel")) {}
ECLIPSELINK(Collections.singleton("org.eclipse.persistence.jpa.JpaEntityManager"), Collections.singleton("org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl")) {}
GENERIC_JPA(Collections.singleton("jakarta.persistence.EntityManager"), Collections.emptySet()) {}
}

Spring Boot 에서는 default Provider 를 Hibernate 로 사용하고 있어서, custom 하는 경우 이외에는 따로 정의해줄 필요가 없다.

Spring Boot configures Hibernate as the default JPA provider, so it’s no longer necessary to define the entityManagerFactory bean unless we want to customize it.

EntityManagerFactory

EntityManager 인스턴스를 생성하는 팩토리이다.

아래 EntityManagerFactory 의 createEntityManager() 를 이용해 EntityManager 인스턴스를 생성한다.

public interface EntityManagerFactory extends AutoCloseable {
    public EntityManager createEntityManager();
  }

EntityManagerFactory 인스턴스(Hibernate 의 SessionFactory 인스턴스)는 thread-safe 하다. (EntityManager 는 thread-safe 하지 않으니 주의하자.)

The EntityManagerFactory instances, and consequently, Hibernate’s SessionFactory instances, are thread-safe. So it’s completely safe in concurrent contexts to write:

(참고) Hibernate 의 SessionFactory

EntityManager

엔티티와 데이터베이스를 상호작용하는 주요 인터페이스

엔티티의 CRUD 작업을 수행한다.
Persistence Context를 관리하며, 엔티티 인스턴스의 생명 주기를 조정한다. (우리는 EntityManager 를 사용함으로써 Persistence Context 에 접근하는 것)

The EntityManager API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.

아래에서 설명할 persistence context 에서 등장하는 entityManager 의 주요 메서드들은 알아두는 것이 좋다.

주요 메서드 > persist(), find(), merge(), remove(), createQuery(), createNamedQuery(), flush(), clear(), getTransaction().

(참고) 아래는 이번 미션에서 구현한 ReservationRepository 이다.
Spring Data JPA를 통해 자동으로 구현되는 인터페이스이고, 내부적으로 EntityManager를 사용하여 데이터베이스와 상호 작용한다.

public interface ReservationRepository extends JpaRepository<Reservation, Long> {
    Reservation findReservationById(final Long id);

    List<Reservation> findAllByDateAndTheme(final LocalDate date, final Theme theme);
}

ReservationRepository 자체가 EntityManager 역할을 하는 것은 아니지만 EntityManager를 사용하여 데이터 접근 로직을 수행하는 기능을 제공하기 때문에, ReservationRepository는 EntityManager를 간접적으로 사용하는 고수준 추상화 레이어라고 할 수 있다.

Spring Data JPA 가 EntityManager 를 주입받아 데이터베이스 연산을 처리하기 때문에 우리가 직접 EntityManager 를 사용할 필요가 없다.

Persistence Context

Persistence Context 는 EntityManager 에 의해 관리되는 1차 캐시로서, 엔티티 객체들이 저장되는 컨텍스트이다.
Application 과 DB 사이에 위치하며 관리되는 엔티티에 대한 모든 변경 사항을 추적 한다.

The persistence context is the first-level cache where all the entities are fetched from the database or saved to the database. It sits between our application and persistent storage.
Persistence context keeps track of any changes made into a managed entity. If anything changes during a transaction, then the entity is marked as dirty. When the transaction completes, these changes are flushed into persistent storage.

엔티티 객체는 영속성 컨텍스트에 의해 관리되는데, 이를 영속화 되었다고 한다.
영속화가 되었다는 말은 1차 캐시에 저장되었다는 의미이다. (= 영속화가 되었다해서 디비에 바로 반영되는것이 아닐 수 있다.)

영속화 개념에 대해서 조금 더 알아보자.

영속화 과정과 각 상태

New (비영속) 상태

엔티티 객체가 생성된 후 아직 영속성 컨텍스트에 저장되지 않은 상태

  • 아직 데이터베이스에 저장되지 않음.
  • 영속성 컨텍스트에 의해 관리되지 않는다. ( == JPA가 이 객체를 추적하거나 관리하지 않음)

다시 말해서 new (비영속) 상태는 엔티티 객체가 단순히 메모리상에서만 존재하고, 아직 데이터베이스에 저장되거나 JPA에 의해 관리되지 않는 초기 상태이다.

Member member = new Member();

💡 Managed (영속) 상태

엔티티 객체가 영속성 컨텍스트에 저장되어 관리되는 상태

  • 엔티티 객체의 변경 사항이 영속성 컨텍스트에 의해 추적된다.
  • 트랜잭션이 커밋될 때, 변경 사항이 자동으로 데이터베이스에 반영된다. ( == 변경감지(Dirty Checking) 라고 한다.)
    (아래에 변경감지(Dirty Checking) 에 대한 설명을 추가적으로 적어놓았다.)

This is managed by the currently running Session, and every change made on it will be tracked and propagated to the database automatically.

전환 방법

  • EntityManager.persist(entity) 를 호출하여 new 상태의 엔티티를 영속성 컨텍스트에 추가
  • EntityManager.find(Class entityClass, Object primaryKey) 또는 JPQL 쿼리를 통해 데이터베이스에서 엔티티를 조회하여 영속성 컨텍스트에 추가
EntityManager em = ...;
em.getTransaction().begin();
em.persist(member); // managed 상태
em.getTransaction().commit();

변경감지(Dirty Checking)

영속성 컨텍스트 내에서 관리되는 엔티티 객체의 변경 사항을 자동으로 감지하고, 데이터베이스에 반영하는 메커니즘
(개발자가 명시적으로 UPDATE 쿼리를 작성하지 않아도, 엔티티의 상태 변화가 데이터베이스에 자동으로 반영되도록 해준다.)

동작 방식 (위의 이미지와 아래의 설명을 함께 보자)

  1. 스냅샷(Snapshot) 저장

엔티티가 영속성 컨텍스트에 들어올 때 (영속 상태가 되면), JPA는 엔티티의 초기 상태를 복사하여 저장해둔다. 이 초기 상태를 스냅샷이라 한다.

  1. 변경 감지

트랜잭션이 커밋되거나 flush가 호출될 때, JPA는 현재 엔티티 객체의 상태와 스냅샷을 비교한다.
이때, 엔티티 객체의 상태가 스냅샷과 다른 경우 JPA는 해당 엔티티가 변경되었다고 감지한다.

On call to transaction commit() or flush(), the Session will find any dirty entities from its tracking list and synchronize the state to the database.
Notice that we didn’t need to call any method to notify Session that we changed something in our entity – since it’s a managed entity, all changes are propagated to the database automatically.

  • 쓰기 지연 저장소

    변경된 엔티티의 SQL 쿼리를 모아두는 메커니즘. ( == 영속성 컨텍스트에서 관리되는 엔티티의 변경 사항을 모아두는 임시 저장 공간)

    • 생성된 SQL 쿼리는 즉시 실행되지 않고, 일시적으로 쓰기 지연 저장소에 저장이 된다. (성능을 최적화하고 데이터베이스와의 상호작용을 효율적으로 관리하기 위함)
    • 트랜잭션이 커밋되거나 EntityManager.flush() 메서드가 호출되면, 쓰기 지연 저장소에 모아둔 SQL 쿼리를 데이터베이스에 일괄적으로 전송하여 실행한다.
  1. 변경 사항 반영

JPA는 감지된 변경 사항을 바탕으로 적절한 UPDATE 쿼리를 생성하여 데이터베이스에 반영한다.

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();

// 엔티티 조회
Member member = em.find(Member.class, 1L);

// 엔티티 변경 (영속성 컨텍스트에서 관리되는 상태)
member.setName("New Name");

// 트랜잭션 커밋 (쓰기 지연 저장소에 모아둔 변경 사항이 자동으로 데이터베이스에 반영)
em.getTransaction().commit();
em.close();

Detached (준영속) 상태

엔티티 객체가 한때 영속성 컨텍스트에 의해 관리되었지만, 현재는 더 이상 관리되지 않는 상태

  • 영속성 컨텍스트의 범위를 벗어난 경우나 명시적으로 분리(detach)한 경우 발생한다.
  • 데이터베이스와 연결이 끊어져 있으며, 변경 사항이 자동으로 데이터베이스에 반영되지 않는다.

전환 방법

  • EntityManager.detach(entity)를 호출하여 명시적으로 엔티티를 분리
  • 트랜잭션 종료, EntityManager.close(), 또는 EntityManager.clear() 호출로 영속성 컨텍스트가 종료되었을 때
em.detach(member);

Removed (삭제) 상태

엔티티 객체가 영속성 컨텍스트에서 제거되었고, 트랜잭션이 커밋되면 데이터베이스에서 삭제될 예정인 상태

  • 영속성 컨텍스트에 의해 여전히 관리되지만, 삭제 대상이다.
  • 트랜잭션이 커밋되면 데이터베이스에서 해당 엔티티가 삭제된다.

전환 방법

  • EntityManager.remove(entity)를 호출하여 엔티티를 삭제 대상로 지정
em.remove(member);

위에서 계속 트랜잭션에 대한 얘기가 나오는데, 트랜잭션과 영속성 컨텍스트의 관계에 대해서도 알아보자.

트랜잭션과 영속성 컨텍스트의 관계

트랜잭션 단위와 영속성 컨텍스트의 생명주기

일반적으로, 하나의 트랜잭션은 하나의 영속성 컨텍스트 를 가진다.
트랜잭션이 시작될 때 영속성 컨텍스트가 생성되고, 트랜잭션이 종료되면 영속성 컨텍스트도 종료된다.

  • 트랜잭션이 커밋되거나 롤백될 때 영속성 컨텍스트가 플러시(변경 내용을 데이터베이스에 반영)되고, 트랜잭션이 종료되면 영속성 컨텍스트가 닫히면서 모든 managed(영속) 엔티티가 detached(준영속) 상태가 된다.

트랜잭션이 없는 경우

트랜잭션이 없는 경우에도 영속성 컨텍스트는 존재할 수 있지만, 일반적으로 auto-commit 모드로 동작하게 된다. (각 쿼리 실행마다 자동으로 커밋이 발생하여 데이터베이스와의 일관성을 보장하지 못할 수 있다.)

플러시(Flush) 와 트랜잭션

영속성 컨텍스트는 트랜잭션이 커밋될 때 자동으로 플러시된다. (영속성 컨텍스트에 보관된 변경 사항들이 데이터베이스에 반영되는 시점에)

  • EntityManager.flush()를 호출하여 명시적으로 플러시를 강제할 수도 있다.

Reference

Persistence Units
JPA/Hibernate Persistence Context
Interface EntityManager
Enum PersistenceProvider
Guide to the Hibernate EntityManager
Hibernate Entity Lifecycle

profile
내가 하고 싶은 거

0개의 댓글