[Spring] EntityManager & Persistence Context

JY·2025년 3월 26일
0

Spring

목록 보기
3/5
post-thumbnail

💡 영속성 컨텍스트 (Persistence Context)

우선적으로 Persistence Context의 공식 정의이다.

EntityManager 인스턴스는 Persistence Context와 연관됩니다. Persistence Context는 모든 Persistence Entity ID에 대해 고유한 Entity 인스턴스가 있는 Entity 인스턴스 집합입니다. Persistence Context 내에서 Entity 인스턴스와 해당 수명 주기가 관리됩니다. EntityManager API는 Persistence Entity 인스턴스를 만들고 제거하고, 기본 키로 Entity를 찾고, Entity를 쿼리하는 데 사용됩니다.

🔗 JPA/Hibernate Persistance Context

영속성 컨텍스트는 JPA의 핵심 개념 중 하나로, DB와 애플리케이션 서버 사이에 위치한다. 쉽게 말해, 데이터베이스와 애플리케이션 사이에서 Entity 객체를 관리하는 "가상의 공간"이다.

이 영속성 컨텍스트는 애플리케이션에서 관리하는 Entity 객체들을 "영속화"하는 역할을 하는데 즉, JPA를 통해 CRUD 작업을 수행할 때 Entity 객체의 상태(새로운 객체, 수정된 객체, 삭제된 객체 등)를 추적하고 관리하는 곳이 바로 영속성 컨텍스트이다.

@PersistenceContext 어노테이션을 사용하여 EntityManager 인터페이스를 주입받아 Context에 접근할 수 있다.


💡 EntityManager와 EntityManagerFactory

EntityManagerFactoryEntityManager를 인스턴스화하여 사용할 수 있게 해주는 인터페이스이다. JPA의 설정과 초기화를 담당하며 Application이 시작될 때, 단 하나만의 객체가 생성된다. 실제 Factory와 같은 역할을 담당하여 여러 개의 EntityManager인스턴스를 만들어낼 수 있다.

EntityManager는 JPA에서 Entity를 담당하는 주요 인터페이스이다. 영속성 컨텍스트에서 Entity의 CRUD 및 다른 모든 작업을 실질적으로 처리하는 역할을 하며, 보통 트랜잭션 범위에서 사용된다.

🔥 인터페이스의 인스턴스화 ?

EntityManagerFactoryEntityManager는 인터페이스이지만, JPA 구현체가 제공하는 구체적인 클래스를 통해 인스턴스화가 가능하다.

  • EntityManagerFactoryPersistence.createEntityManagerFactory()를 통해 인스턴스를 반환한다.
  • 이를 통해 반환된 EntityManagerFactory인스턴스의 createEntityManager()를 통해 최종적으로 EntityManager의 인스턴스를 만들어낼 수 있다.

잘 이해되지않는다면 다음 사항만 기억해두도록하자

구분EntityManagerEntityManagerFactory
역할Entity를 관리하고 CRUD 작업을 수행EntityManager를 생성하는 팩토리 역할
생성EntityManagerFactory에서 생성Application 시작 시 한번만 생성
범위보통 트랜잭션 범위에서 사용Application 전체에서 공유되며, 주로 하나만 생성
생명주기트랜잭션이 끝날 때까지 사용보통 하나의 요청에 하나씩 생성 애플리케이션 시작 시
하나만 생성되고, 종료 시까지 사용

💡 Entity의 생명주기와 상태

영속성 컨텍스트가 하는 일에 대해 알아보기전에 먼저 Entity의 4가지 상태에 대해 알아보자

  • 비영속 (Transient) : JPA가 모르는 상태. DB와 관계 없음
    ex > User user = new User();

  • 영속 (Persistent) : 영속성 컨텍스트가 관리하는 상태. DB와 동기화 ⭕
    ex > entityManager.persist(user)

  • 준영속 (Detached) : 영속 상태였다가 컨텍스트에서 분리된 상태. DB와 동기화 ❌
    ex > entityManager.detach(user) or em.clear()

  • 삭제 (Removed) : 삭제 예정 상태. commit시 DB에서 삭제됨
    ex > entityManager.remove(user)

Entity는 일반적으로 비영속 -> 영속 -> 준영속 -> 삭제의 생명주기를 가진다.


🤔 그럼 영속성 컨텍스트가 하는일은?

✅ 1차 캐시

영속성 컨텍스트 내에서 관리되는 Entity 객체의 캐시다. JPA는 Entity를 영속성 컨텍스트에 로딩할 때 이 캐시를 사용하여 DB 조회를 최소화하고, 동일한 Entity를 여러 번 조회하는 경우 성능을 최적화한다. 1차 캐시는 트랜잭션 내에서만 유효하고, 트랜잭션이 끝나면 지워진다.

User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);  // 1차 캐시에서 가져옴

✅ 동일성 보장

JPA에서는 동일한 식별자를 가진 엔티티는 단 하나의 인스턴스만 존재해야 한다는 규칙이 있다. 즉, 같은 ID를 가진 Entity는 항상 동일한 인스턴스를 참조하도록 보장한다. 이는 1차 캐시와 연결되어 있으며, 같은 트랜잭션 내에서 같은 엔티티를 여러 번 조회해도 동일한 객체가 반환된다.

// (1)에 이어지는 코드
// 두 객체는 동일한 인스턴스다. DB를 두 번 조회하지 않고 캐시에서 데이터를 가져옴
System.out.println(user1 == user2);  // true

✅ 변경감지 (Dirty Checking)

영속성 컨텍스트는 1차 캐시에 저장된 Entity의 스냅샷을 만들어둔다. 이후 변경된 필드를 자동으로 감지하여 트랜잭션 커밋 시 변경사항을 DB에 반영하는 기능이다. JPA는 Entity의 상태를 비교하여, DB에 반영해야 할 변경 사항이 있는지 자동으로 감지한다.

User user = entityManager.find(User.class, 1L);

// ID :1L에 저장된 원래 값이 "User"라고 하면 "User" -> "Name"으로 변경
user.setName("Name");

// 트랜잭션 커밋 시, JPA는 변경된 값을 자동으로 DB에 반영합니다.

✅ 지연 로딩 (Lazy Loading)

연관된 Entity를 실제 사용될 때까지 DB에서 조회하지 않고 필요한 시점에만 데이터를 조회하는 방식이다. JPA는 기본적으로 지연 로딩을 사용하여 성능을 최적화한다. 연관된 Entity가 실제로 필요할 때 쿼리가 실행된다.


🔎 영속성 컨텍스트의 타입

1️⃣ PersistenceContextType.TRANSACTION (Default)

트랜잭션 스코프(Transaction-scoped)라고하며 트랜잭션이 시작될 때 영속성 컨텍스트가 생성되고, 트랜잭션이 끝나면 함께 종료된다.@Transactional로 묶인 범위 내에서만 영속성 컨텍스트가 유지된다.
(Spring Data JPA에서는 EntityManager도 같은 설정을 따른다.)

@Transactional
public void saveUser() {
    User user = new User();
    user.setName("User");

    userRepository.save(user);  // 영속 상태 (persist)
    user.setName("Name");  // Dirty Checking
} // 트랜잭션 종료 시 INSERT & UPDATE 실행 (커밋)

2️⃣ PersistenceContextType.EXTENDED

확장 스코프(Extended-scoped)라고하며 트랜잭션에 의존하지 않고, 엔티티 매니저(EntityManager)가 살아있는 동안 영속성 컨텍스트 유지된다.

장기 유지 상태가 필요할 때 사용한다. (ex > 웹소켓, 대화형 상태 관리 등)

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager em;

public void saveUserExtended() {
    User user = new User();
    user.setName("Yujiyong");

    em.persist(user);  // 트랜잭션 없어도 영속 상태
    user.setName("ExtendedYujiyong");  // 변경 감지도 유지됨

    // 트랜잭션 시작 후 commit 해야만 DB 반영
}

🎯마무리

사실 Spring Data JPA를 사용하면 개발자가 영속성 컨텍스트나 EntityManager를 직접적으로 다룰 일이 거의 없다. 하지만 내부 동작 방식을 이해하고 있으면, 성능 최적화나 예기치 않은 문제 해결에 큰 도움이 되니 알아두도록 하자

0개의 댓글