[JPA] 영속성 컨텍스트

짱챌·2025년 4월 12일

JPA

목록 보기
2/5

영속성 컨텍스트와 엔티티 생명주기, 1차 캐시 + 쓰기 지연 + 더티 체킹
흐름을 파악해보자~

0. JPA 구조

  • Entity: DB에 매핑될 객체 (영속 객체)
  • EntityManager
    • DB와 연결된 영속성 컨텍스트를 통해 Entity 관리
    • 트랜잭션 관리
    • Query 객체 생성
    • 스레드 간 공유 금지 (동시성 문제 발생)
  • EntityManagerFactory: Entity Manager를 생성
    • 비용이 크기 때문에 일반적으로 앱 실행 시 한번 생성되어, 종료 시까지 재사용함
  • EntityTransaction
    • EntityManager당 하나 존재 (1:1)
    • 한 작업 단위를 정의하여, 전부 성공하거나 전부 실패하게 만듦
  • Query: 조건에 맞는 엔티티 검색
  • Persistence: EntityManagerFactory를 생성
  • Persistence Unit: DB 연결을 위한 설정 단위
    • persistence.xml 또는 application.yml/properties에 설정
    • jdbc 드라이버, url, username, password

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

  • JPA가 관리하는 엔티티 객체들을 저장하고 추적하는 메모리 공간
  • EntityManager가 관리하며, 컨텍스트 내부 객체들을 영속 상태라고 한다.
  • 엔티티를 사용하고 바로 폐기하는 것이 아닌, 저장 후 재사용
  • 같은 트랜잭션 내에서 동일한 엔티티를 여러번 조회해도, 항상 동일한 인스턴스를 반환함

2. 엔티티 생명 주기

✅비영속 (Transient)

  • 영속성 컨텍스트와 관계가 없는 상태 (DB와도 관계 X)

✅영속 (Managed)

  • 영속성 컨텍스트에 의해 엔티티가 관리되는 상태
  • 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장 (persist())
  • find()로 조회한 엔티티도 영속 상태이다.

✅준영속 (Detached)

  • 영속 상태였던 엔티티가 영속성 컨텍스트에서 분리된 상태 (detach())
  • clear(), close() 시에도 준영속 상태가 된다.
  • 준영속 상태의 엔티티는 식별자 값은 유지되지만, 변경 사항이 감지되지 않아 DB에 반영되지 않는다.

📌사용 시점

  1. 트랜잭션 종료 이후에 엔티티를 계속 사용할 때

트랜잭션이 끝나면 엔티티 매니저도 닫히면서, 영속성 컨텍스트가 종료되고 엔티티는 자동으로 준영속 상태가 된다

  1. 성능 최적화 목적

대량의 데이터를 영속 상태로 유지하면 메모리 과부하가 발생할 수 있다.

그래서 일정 단위마다 flush() → clear()를 호출하여 준영속화 시킨다.

✅삭제 (removed)

  • 엔티티가 영속성 컨텍스트와 DB에서 삭제된 상태 (remove())

3. 1차 캐시

  • EntityManager가 관리하는 영속성 컨텍스트 내부에 있는 첫 번째 캐시
  • 1차 캐시에 엔티티가 있다면 DB 접근 없이 연산을 수행할 수 있다.
  • = 동일한 엔티티에 대한 반복 조회 시 DB를 여러 번 조회하지 않고, 메모리에서 즉시 반환하여 성능을 향상시킨다.
  • 트랜잭션이 시작하고 종료될 때까지 유효하다.
  • Map<식별자, 엔티티>구조로 관리된다.

4. 쓰기 지연 (Write-Behind)

  • 엔티티 인스턴스에 대한 쿼리를 즉시 DB에 날리지 않고, 영속성 컨텍스트 내부의 쓰기 지연 저장소에 저장
  • 실제 쿼리는 트랜잭션 커밋 시점 또는 flush() 호출 시점에 한꺼번에 전송 → DB 성능 향상

5. 더티 체킹 (Dirty Checking)

  • 영속성 컨텍스트에 등록된 객체의 값이 변경되었는지를 감지하여, 변경된 경우에만 업데이트 쿼리를 자동 생성해줌
  1. JPA는 엔티티가 영속성 컨텍스트에 처음 들어왔을 때 상태를 기억함 (스냅샷)
  2. 커밋 전, 스냅샷과 현재 상태를 비교하여 변경 감지

✅ 예시 코드

entitymanager.getTransaction().begin();

// [1] 비영속 상태 (Persistence Context에 없음)
Member member = new Member("감자");

// [2] 영속 상태 등록 
//  - 1차 캐시에 등록됨
//  - 쓰기 지연 저장소에 INSERT 쿼리 준비
entitymanager.persist(member);  

// [3] 1차 캐시에서 조회 (DB 접근 없음)
entitymanager.find(Member.class, member.getId()); 

// [4] 값 변경 → 더티 체킹 대상
member.setName("고구마");

// [5] 트랜잭션 커밋
//  - 더티 체크 → UPDATE 쿼리 생성
//  - flush 실행 → INSERT & UPDATE 쿼리 실제로 DB에 전송
entitymanager.getTransaction().commit();

✅ 실제 테스트

entitymanager.getTransaction().begin();

Member member = new Member("감자");
System.out.println("===== persist 전 =====");
entitymanager.persist(member);
System.out.println("===== persist 후 =====");

System.out.println("===== 변경 전 =====");
member.setName("고구마");
System.out.println("===== 변경 후 =====");

System.out.println("===== 커밋 전 =====");
entitymanager.getTransaction().commit();
System.out.println("===== 커밋 후 =====");

위 테스트는 엔티티 id 전략을 SEQUENCE로 했을 경우이다.

동일 코드를 IDENTITY로 설정하여 다시 돌려보았다.


IDENTITY 전략을 사용하면, JPA는 ID 값을 DB에서 받아와야 하니까 persist() 순간에 바로 insert 쿼리를 날린다.

처음에 IDENTITY로 먼저 테스트를 진행했는데 INSERT 쿼리가 영속 시점에 날라가서 당황했다..

IDENTITY vs SEQUENCEINSERT 시점 차이

전략ID 생성 방식INSERT 실행 시점차이 이유
IDENTITYDB가 ID 생성 (ex: AUTO_INCREMENT)persist() 호출 시 즉시 INSERTID 값을 DB가 정하므로, 미리 insert해서 ID 받아와야 함
SEQUENCE시퀀스에서 미리 ID 조회commit() 또는 flush() 시 INSERTHibernate가 미리 ID를 확보하므로 INSERT는 나중에 가능 (쓰기 지연 효과 O)
profile
애옹: Magic Cat Academy

0개의 댓글