JPA - 영속성 컨텍스트(Persistence Context)


 

1. JPA 영속성 컨텍스트(Persistence Context)

 

1.1. 영속성 컨텍스트

 

 

영속성 컨텍스트는 엔터티의 상태를 관리하는 JPA의 1차 캐시로, 애플리케이션과 데이터베이스 사이에서 중간 역할을 수행합니다. 여기서 엔터티란 데이터베이스의 테이블에 대응되는 객체로, JPA에서 데이터를 매핑하고 관리하는 단위입니다.

 

 쉽게 말하면 메모리 내의 "가상 데이터베이스" 같은 역할을 합니다. 영속성 컨텍스트에 엔터티를 올리면, JPA는 이를 관리하며, 실제 데이터베이스와의 작업을 대신 처리합니다. 

 

2.2. 영속성 컨텍스트의 역할

영속성 컨텍스트는 단순한 데이터 저장소 이상의 역할을 합니다.


1) 엔터티 상태 관리
엔터티의 상태를 관리하여 생성, 변경, 삭제 등을 추적합니다. 엔터티는 영속성 컨텍스트에 등록되면 JPA의 관리 대상이 되어, 변경이 자동으로 감지됩니다(Dirty Checking).

 

2) 데이터 일관성 유지
동일한 트랜잭션 내에서는 영속성 컨텍스트가 관리하는 엔터티는 동일성을 보장합니다. 즉, 같은 객체입니다.


3) 데이터베이스와의 중간 계층 역할

영속성 컨텍스트는 데이터베이스에 직접 접근하지 않습니다. 대신 SQL 쿼리를 생성하고 이를 데이터베이스에 전달하는 중개자 역할을 합니다. 또한 트랜잭션의 종료 시점에만 데이터베이스와 상호작용하여 성능을 최적화합니다(쓰기 지연).

 

 정리하면 영속성 컨텍스트는 트랜잭션 내에서 엔터티의 변경 사항을 추적하며, 트랜잭션 종료 시 변경된 데이터를 데이터베이스에 반영합니다. 동일한 영속성 컨텍스트 내에서는 동일한 엔터티 식별자에 대해 항상 동일한 객체를 반환하여 데이터 일관성을 보장합니다.

 

2.3. EntityManagerFactory와 EntityManager

 

 

- JPA에서 영속성 컨텍스트는 EntityManager 객체를 통해 접근하고 관리합니다.

- EntityManager는 EntityManagerFactory를 통해 생성됩니다.

 

1) EntityManagerFactory: 영속성 컨텍스트의 팩토리

  • EntityManager를 생성하는 역할을 하는 팩토리 객체입니다.
  • 영속성 단위의 설정 파일(persistence.xml)을 기반으로 생성됩니다.
  • EntityManagerFactory는 무겁기 때문에 애플리케이션 시작 시 한 번만 생성하는 것이 좋습니다..

2) EntityManager: 영속성 컨텍스트의 관리자

  • EntityManager는 영속성 컨텍스트를 관리합니다.
  • 트랜잭션 단위로 사용되며, 각 트랜잭션마다 새로운 인스턴스가 생성됩니다.
  • 주요 기능:
    1. 엔터티 저장: persist().
    2. 엔터티 조회: find()
    3. 엔터티 수정: 변경 감지에 의해 자동 처리.
    4. 엔터티 삭제: remove().
    5. 엔터티 분리: detach(), clear(), close().
public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        
        // 로직...
        
        em.close();
        emf.close();
}

 

2. 엔터티의 생명주기와 상태 변화

 

life

 

2.1. 비영속(Transient)

Member member = new Member();
  • 엔터티 객체는 메모리에 존재하지만 영속성 컨텍스트에 포함되지 않아 JPA에 의해 관리되지 않습니다.
  • 데이터베이스와의 연관이 없습니다.

 

2.2. 영속(Persistent)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();

Member member = new Member();
em.persist(member);
  • 엔터티가 영속성 컨텍스트에 포함되어 JPA가 관리하는 상태입니다.
  • em.persist(member)를 통해 member 객체는 영속성 컨텍스트에 추가됩니다.

 

2.3. 준영속(Detached)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();

Member member = new Member();
em.persist(member);
em.detach(member); // 영속성 컨텍스트에서 분리
  • 엔터티가 영속성 컨텍스트에서 분리된 상태입니다. 트랜잭션이 종료되거나 detach() 메서드로 분리될 수 있습니다.
  • 더 이상 JPA가 엔터티를 관리하지 않으며, 변경된 내용은 데이터베이스에 반영되지 않습니다.
  • em.detach(member)로 member는 영속성 컨텍스트에서 분리되어 더 이상 관리되지 않습니다.
  • em.merge(member)로 다시 영속상태로 바꿀 수 있습니다.

 

2.4. 삭제(Removed)

em.remove(member);
  • 엔터티가 EntityManager.remove() 메서드를 통해 삭제된 상태입니다. 데이터베이스에서 해당 엔터티가 삭제됩니다.
  • 삭제된 엔터티는 영속성 컨텍스트에서 제거되고, 데이터베이스에서도 삭제됩니다.

 

- persist()랑 merge()

persist()와 merge()는 둘 다 엔터티를 영속성 컨텍스트에 추가하는 공통점이 있지만, 사용 목적에 차이가 있습니다.

  • persist(): 새로운 엔터티를 영속성 컨텍스트에 추가하는 메서드로, 새로운 데이터를 추가(INSERT)하는 기능입니다. 
    • PK 값이 없으면: 새로운 데이터가 추가됩니다. 즉, INSERT 쿼리가 실행되어 새로 저장됩니다.
    • PK 값이 있으면: 이미 존재하는 데이터가 있기 때문에 Exception이 발생합니다.

 

 

  • merge(): 기존 엔터티를 병합하여 영속성 컨텍스트에 추가하는 메서드로, 기존 데이터를 수정(UPDATE)하는 기능입니다.
    • PK 값이 없으면: 새로운 데이터가 추가됩니다. 즉, INSERT 쿼리가 실행되어 새로 저장됩니다.
    • PK 값이 있으면: 기존 데이터가 존재하는 경우, 해당 데이터를 찾아 업데이트(UPDATE) 쿼리가 실행됩니다.(DB값과 비교하여 수정되었을 때만)

 

 

3. 영속성 컨텍스트의 주요 기능

 

4.1 1차 캐시

 1차 캐시는 영속성 컨텍스트가 관리하는 메모리 영역으로, 트랜잭션 내에서 조회된 엔터티를 저장합니다. 동일한 트랜잭션 내에서 같은 엔터티를 조회하면, 데이터베이스에 재요청하지 않고 1차 캐시에서 즉시 반환합니다.

 

 이는 반복적인 데이터베이스 호출을 줄여 성능을 최적화하는 데 일부 기여합니다.

 

 

4.2 엔터티 동일성 보장

 JPA는 동일한 트랜잭션 내에서 동일한 엔터티를 1차 캐시에서 관리하여, 객체 비교 시 == 연산으로 동일성을 보장합니다. 즉, 같은 엔터티를 메모리에서 공유하므로 equals가 아닌 객체 참조 비교로도 동일 여부를 확인할 수 있습니다. 

Member member1 = em.find(Member.class, 1L);
Member member2 = em.find(Member.class, 1L);
System.out.println(member1 == member2); // true

 

 

4.3 트랜잭션을 지원하는 쓰기 지연

 쓰기 지연은 SQL 쿼리를 즉시 실행하지 않고 영속성 컨텍스트 내부의 SQL 저장소에 쌓아 두는 기능입니다. 트랜잭션 커밋 시점에 모든 SQL 쿼리가 한꺼번에 실행됩니다. 이를 통해 트랜잭션 내에서 여러 작업을 한꺼 번에 처리할 수 있습니다. 

 

 쓰기 지연된 SQL 쿼리는 플러시(flush)를 통해 데이터베이스와 동기화됩니다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이며, 다음과 같은 상황에서 실행됩니다:

  1. em.flush() 호출 시: 개발자가 명시적으로 플러시를 요청할 때.
  2. 트랜잭션 커밋 시: transaction.commit() 호출 시 자동으로 플러시가 발생.
  3. JPQL 쿼리 실행 시: JPQL이나 Criteria API를 사용하여 쿼리를 실행할 때, 쿼리의 정확성을 보장하기 위해 플러시가 발생합니다.

 

4.4 변경 감지 (Dirty Checking)

 JPA는 변경 감지 기능을 통해 영속 상태의 엔터티가 변경될 경우 이를 자동으로 감지합니다. 변경된 필드를 추적하여 필요한 SQL 쿼리(주로 UPDATE)를 생성하며, 개발자가 일일이 데이터베이스 업데이트를 명시할 필요가 없습니다. 이 과정은 트랜잭션 커밋 또는 flush() 시점에 이루어집니다.

 

따라서 다음과 같이 굳이 merge로 변경된 엔터티를 컨텍스트에 등록하는 코드를 작성할 필요가 없습니다.

Member member = em.find(Member.class, 1L);
member.setName("pppppp");
// em.merge(member) -> 굳이 쓸 필요 없음.

 

- 스냅샷(Snapshot)과 더티 체킹

변경감지는 스냅샷과 더티 체킹과정을 통해 이루어집니다.

 

  1. 초기 스냅샷 저장
    JPA는 엔터티가 영속성 컨텍스트에 처음 관리될 때, 해당 엔터티의 상태를 스냅샷(Snapshot)으로 저장합니다. 위 예제에서는 find 메서드를 통해 엔터티가 데이터베이스에서 조회되어 영속성 컨텍스트에 등록된 상태입니다.
  2. 변경 사항 비교
    트랜잭션이 진행되는 동안 엔터티의 상태가 변경되면, JPA는 변경된 엔터티의 상태를 저장하고 저장된 스냅샷을 비교합니다. 이 비교 과정에서 변경된 필드를 감지하고, 이를 더티 상태로 표시합니다.
  3. SQL 쿼리 생성
    더티 상태로 표시된 엔터티는 트랜잭션 커밋 또는 em.flush() 호출 시 자동으로 UPDATE SQL 쿼리가 생성되고 실행됩니다. 이 과정에서 변경되지 않은 필드에는 영향을 주지 않으며, 필요한 필드만 업데이트됩니다.

 

4.5 지연 로딩 (Lazy Loading)

지연 로딩은 실제로 필요한 데이터가 요청될 때 데이터베이스에서 로드되는 방식입니다. 엔터티를 조회할 때 연관된 데이터는 프록시 객체로 처리되며, 해당 데이터에 접근하면 그제야 쿼리가 실행됩니다. 이는 초기 로딩의 성능 부담을 줄이고, 필요한 데이터만 로드하여 메모리와 처리 시간을 효율적으로 사용할 수 있도록 돕습니다. 지연 로딩은 복잡한 엔터티 연관 관계에서 유용합니다.

'컴퓨터 > JAVA' 카테고리의 다른 글

JPA - 연관관계 매핑 2(프록시 객체)  (0) 2024.12.05
JPA - 연관관계 매핑 1(외래키 설정)  (0) 2024.11.28
JAVA - 일급 컬렉션(First-Class Collection)  (0) 2024.10.31
JAVA - Enum 클래스  (0) 2024.10.25
JUnit5 기초  (0) 2024.10.24