영속성 컨텍스트(Persistence Context)는 JPA(Java Persistence API)에서 핵심적인 개념으로, 엔티티(Entity) 객체를 관리하고 데이터베이스와의 상호작용을 조정하는 역할을 합니다. 영속성 컨텍스트는 메모리 내에서 엔티티를 관리하며, 데이터베이스와 직접적인 CRUD 작업을 효율적으로 처리합니다. 이 과정에서 엔티티의 상태(생명 주기)가 어떻게 변화하는지 이해하는 것이 중요합니다.
영속성 컨텍스트란?
- 영속성 컨텍스트(Persistence Context)는 JPA가 관리하는 엔티티 객체를 저장하고 관리하는 메모리 공간입니다. 간단히 말해, 엔티티의 상태를 추적하고 데이터베이스와 동기화하는 역할을 합니다.
영속성 컨텍스트의 주요 특징
- 1차 캐시
- 영속성 컨텍스트는 1차 캐시로 동작하며, 데이터베이스에서 조회된 엔티티를 캐시에 저장합니다.
- 동일한 엔티티를 여러 번 조회하더라도 데이터베이스를 다시 조회하지 않고 캐시된 엔티티를 반환합니다.
- 변경 감지(Dirty Checking)
- 엔티티 객체의 상태가 변경되었는지 감지하고, 변경된 데이터를 기반으로 필요한 SQL을 생성합니다.
- 이 과정에서 엔티티와 스냅샷(초기 상태)을 비교합니다.
- 쓰기 지연
- 데이터 변경 시 즉시 SQL을 실행하지 않고, SQL 저장소에 SQL을 쌓아두었다가 flush() 또는 commit 시점에 일괄 실행합니다.
- 동일성(identity) 보장
- 동일한 영속성 컨텍스트 내에서는 같은 식별자를 가진 엔티티는 항상 동일한 객체로 관리됩니다.
flush() 메커니즘
flush()는 영속성 컨텍스트의 내용을 데이터베이스와 동기화하는 역할을 합니다. 하지만 트랜잭션이 끝나는 시점에 commit과 함께 동작하므로, 즉시 데이터베이스에 저장하지는 않습니다.
- flush() 호출
- 개발자가 명시적으로 호출하거나, 트랜잭션의 commit 시점에 자동으로 호출됩니다.
- 변경 감지
- 영속성 컨텍스트의 엔티티와 스냅샷을 비교하여 변경된 엔티티를 찾습니다.
- 변경된 데이터를 기반으로 SQL을 생성합니다.
- SQL 생성 및 저장
- 변경된 엔티티에 대해 필요한 SQL(INSERT, UPDATE, DELETE)을 생성하고, 쓰기 지연 저장소에 저장합니다.
- SQL 실행
- 저장소에 쌓인 SQL을 데이터베이스에 전달하여 실행합니다.
- 트랜잭션 커밋
- 데이터베이스의 커밋이 이루어져 변경 사항이 최종적으로 반영됩니다.
주요 메서드와 특징
메서드 | 설명 | 실행 시점 |
persist() | 엔티티를 영속성 컨텍스트에 저장합니다. | 즉시 (쓰기 지연 저장소에 저장) |
flush() | 영속성 컨텍스트의 내용을 데이터베이스와 동기화합니다. | 명시적 호출 또는 트랜잭션 commit 시점 |
commit() | 트랜잭션을 커밋하고 데이터베이스 변경 사항을 확정합니다. | 트랜잭션 종료 시점 |
clear() | 영속성 컨텍스트를 초기화하여 관리 중인 모든 엔티티를 분리(detach)합니다. | 즉시 실행 |
detach() | 특정 엔티티를 영속성 컨텍스트에서 분리합니다. | 즉시 실행 |
merge() | 준영속(detached) 상태의 엔티티를 다시 영속성 컨텍스트에 병합합니다. | 즉시 실행 |
1. CRUD와 영속성 컨텍스트
- CRUD (Create, Read, Update, Delete): 데이터베이스에 데이터를 삽입, 조회, 수정, 삭제하는 작업입니다.
- EntityManager와 Persistence Context:
- EntityManager는 JPA에서 영속성 컨텍스트를 관리하는 주요 인터페이스입니다.
- CRUD 작업은 EntityManager가 영속성 컨텍스트를 통해 처리합니다.
- 영속성 컨텍스트는 메모리 내에 엔티티를 저장하고, 데이터베이스와의 동기화를 담당합니다.
2. 엔티티의 생명 주기
엔티티는 JPA 환경에서 4가지 상태로 구분됩니다.
상태 | 설명 | 특징 |
비영속(new) | - 영속성 컨텍스트와 관계없는 상태. - 단순히 new로 생성된 객체. |
데이터베이스와 전혀 연결되지 않음. |
영속(managed) | - 영속성 컨텍스트에 등록된 상태. - EntityManager가 관리 중. |
변경 사항이 자동으로 데이터베이스에 반영됨 (flush 시). |
준영속(detached) | - 영속성 컨텍스트에서 분리된 상태. - EntityManager가 더 이상 관리하지 않음. |
변경 사항이 더 이상 데이터베이스에 반영되지 않음. |
삭제(removed) | - 영속성 컨텍스트에서 제거된 상태. - 데이터베이스에서 삭제될 예정. |
EntityManager.remove() 호출 시 이 상태로 전환됨. |
CRUD 작업 흐름과 엔티티 상태 변화
1) INSERT (Create)
- 새로운 엔티티 객체 생성 → 비영속 상태.
- EntityManager.persist(entity) 호출 → 영속 상태로 전환.
- 영속성 컨텍스트에 등록 → 데이터베이스에 INSERT SQL 전송.
2) SELECT (Read)
- EntityManager.find() 또는 JPQL 실행 시 영속성 컨텍스트에서 먼저 조회.
- 영속성 컨텍스트에 없으면 데이터베이스에서 SELECT 실행 후 영속 상태로 관리.
3) UPDATE
- 영속 상태의 엔티티에서 속성 값을 수정.
- EntityManager.update() 대신 변경 감지(Dirty Checking)가 자동으로 수행.
- 트랜잭션 종료 시점에 flush() 호출 → 데이터베이스에 UPDATE SQL 전송.
4) DELETE
- EntityManager.remove(entity) 호출 → 삭제 상태로 전환.
- 영속성 컨텍스트와 데이터베이스에서 엔티티 삭제.
Persistence Context의 중요성
- 1차 캐시:
- 동일한 트랜잭션 내에서 같은 엔티티 조회 시 데이터베이스를 반복적으로 호출하지 않음.
- 메모리에서 바로 데이터를 조회.
- 변경 감지(Dirty Checking):
- 영속 상태의 엔티티 변경 사항을 자동으로 감지하고 데이터베이스에 반영.
- 쓰기 지연(Write-Behind):
- 트랜잭션 커밋 시점에 일괄적으로 SQL 실행.
- 성능 최적화를 위해 INSERT/UPDATE/DELETE를 지연 처리.
- 동기화:
- 영속성 컨텍스트와 데이터베이스를 항상 일치시키는 역할.
비유를 통한 이해
영속성 컨텍스트를 JPA의 내부 메모리 저장소로 생각할 수 있습니다.
- 비영속 상태: 책이 작성된 초안 상태 → 아직 출판사(영속성 컨텍스트)에 보내지 않음.
- 영속 상태: 출판사에 등록된 책 → 변경 및 수정 사항이 자동으로 반영되어 최종 출판됨.
- 준영속 상태: 출판사에서 계약이 종료된 책 → 출판사에서 더 이상 관리하지 않음.
- 삭제 상태: 출판사에서 삭제된 책 → 서점에서도 더 이상 찾을 수 없음.
엔티티 상태(생명주기)의 이해
JPA에서 엔티티는 다음과 같은 네 가지 상태를 가집니다:
- New (비영속)
- 엔티티 객체가 생성된 상태로, 영속성 컨텍스트에 등록되지 않았습니다.
- 데이터베이스와 연결되지 않으며, 트랜잭션과도 관련이 없습니다.
- new 키워드로 생성된 엔티티 객체가 여기에 해당합니다.
- 상태 변경 메서드: EntityManager.persist()
- Managed (영속)
- 엔티티가 영속성 컨텍스트에 등록된 상태입니다.
- 영속성 컨텍스트는 이 엔티티를 관리하며, 변경 사항을 감지(Dirty Checking)하여 트랜잭션 종료 시 데이터베이스에 반영합니다.
- find() 또는 JPQL 쿼리로 조회된 엔티티도 이 상태에 해당합니다.
- 주요 메서드: persist(), find(), JPQL, flush()
- Detached (준영속)
- 엔티티가 영속성 컨텍스트에서 분리된 상태입니다.
- 이 상태의 엔티티는 더 이상 영속성 컨텍스트에서 관리되지 않으며, 데이터베이스와 동기화되지 않습니다.
- 주로 트랜잭션 종료 또는 명시적인 detach(), clear(), close() 호출로 전환됩니다.
- 상태 복구 메서드: merge()
- Removed (삭제)
- 엔티티가 영속성 컨텍스트에서 삭제된 상태입니다.
- 삭제 상태는 데이터베이스에서도 제거되도록 준비된 상태를 나타냅니다.
- EntityManager.remove() 호출로 삭제 상태가 됩니다.
- 삭제는 flush()를 통해 데이터베이스에 반영됩니다.
영속성 컨텍스트에서 상태 전환
각 상태 간 전환을 다음과 같은 메서드로 나타냅니다:
- New → Managed (비영속 → 영속)
- EntityManager.persist(entity) 메서드 호출.
- 영속성 컨텍스트에 엔티티 등록 후 데이터베이스에 저장 준비.
- Managed → Detached (영속 → 준영속)
- EntityManager.detach(entity)를 호출하여 특정 엔티티를 영속성 컨텍스트에서 제거.
- EntityManager.clear()로 전체 컨텍스트 초기화.
- EntityManager.close()로 영속성 컨텍스트 종료.
- Detached → Managed (준영속 → 영속)
- EntityManager.merge(entity) 호출.
- 준영속 상태의 엔티티를 영속성 컨텍스트로 복원하며, 새로운 영속 상태의 엔티티 반환.
- Managed → Removed (영속 → 삭제)
- EntityManager.remove(entity) 호출.
- 트랜잭션 커밋 또는 flush() 호출 시 데이터베이스에서 삭제.
- Managed 상태에서 데이터베이스로의 동기화
- EntityManager.flush()를 호출하거나 트랜잭션 커밋 시점에 자동 호출.
- 변경된 엔티티 상태를 데이터베이스에 반영 (INSERT, UPDATE, DELETE).
3. 영속성 컨텍스트의 역할
- 1차 캐시
- 영속성 컨텍스트는 엔티티를 메모리에 캐싱하여 동일한 엔티티를 반복적으로 조회할 때 데이터베이스 접근을 줄임.
- 동일한 트랜잭션 내에서 find() 호출 시 1차 캐시를 먼저 조회.
- 변경 감지 (Dirty Checking)
- 영속 상태의 엔티티 변경을 자동으로 감지.
- 트랜잭션 커밋 시점에 변경 내용을 데이터베이스에 반영 (UPDATE SQL).
- 쓰기 지연 (Write-Behind)
- SQL 실행을 지연시켜 트랜잭션 커밋 시점에 한꺼번에 처리.
- INSERT/UPDATE/DELETE SQL을 효율적으로 일괄 처리.
- 동기화 (Synchronization)
- flush()를 통해 영속성 컨텍스트와 데이터베이스 상태를 강제로 동기화.
예제 코드
@Entity @Getter @Setter
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 기본 생성자
public Member() {}
// 생성자
public Member(String name, String email) {
this.name = name;
this.email = email;
}
}
import jakarta.persistence.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MemberService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void demonstrateEntityLifecycle() {
// 1. 비영속 상태 (Transient)
// 영속성 컨텍스트와 전혀 연관되지 않은 상태
Member member = new Member("홍길동", "hong@example.com");
System.out.println("1. 비영속 상태: " + member);
// 2. 영속 상태 (Managed)
// 엔티티를 persist()로 영속성 컨텍스트에 저장
entityManager.persist(member);
System.out.println("2. 영속 상태: ID가 아직 할당되지 않음 -> ID는 flush 시점에 할당됨");
// flush()는 SQL 저장소에 쌓인 쿼리를 실행 (INSERT 발생)
entityManager.flush();
System.out.println("영속 상태 이후: ID = " + member.getId());
// 3. 변경 감지 (Dirty Checking)
// 영속 상태의 엔티티 값을 변경 -> 자동으로 UPDATE 쿼리가 생성됨
member.setName("강감찬");
System.out.println("3. 변경 감지: 이름 변경 -> 강감찬");
entityManager.flush(); // UPDATE SQL 실행
// 4. 준영속 상태 (Detached)
// 엔티티를 영속성 컨텍스트에서 분리(detach)
entityManager.detach(member);
System.out.println("4. 준영속 상태: 더 이상 변경 감지되지 않음");
member.setName("이순신"); // 변경하더라도 UPDATE SQL 실행되지 않음
entityManager.flush(); // SQL 실행 안 됨
// 5. 삭제 상태 (Removed)
// 엔티티를 영속성 컨텍스트에서 제거
entityManager.merge(member); // 다시 영속 상태로 전환
entityManager.remove(member); // 삭제 예약
System.out.println("5. 삭제 상태: DELETE SQL 실행 예정");
entityManager.flush(); // DELETE SQL 실행
// 6. 트랜잭션 종료 // commit 시점에 flush()가 자동으로 호출됨
System.out.println("6. 트랜잭션 종료: 데이터베이스에 변경 사항 반영 완료");
}
}
상태 변화 흐름
단계 | 메서드 호출 | 상태 | 설명 |
비영속 (Transient) | new 객체 생성 | 비영속 상태 | 영속성 컨텍스트와 관련 없음. 데이터베이스에 저장되지 않음. |
영속 (Managed) | entityManager.persist | 영속 상태 | 엔티티가 영속성 컨텍스트에서 관리됨. 변경 감지가 가능하며 쓰기 지연 적용. |
변경 감지 | 필드 값 변경 | 영속 상태 | flush() 또는 commit 시점에 SQL 생성. |
준영속 (Detached) | entityManager.detach | 준영속 상태 | 영속성 컨텍스트에서 분리되어 변경 감지가 중단됨. |
삭제 (Removed) | entityManager.remove | 삭제 상태 | 삭제가 예약되며 flush() 또는 commit 시점에 DELETE SQL 실행. |
TL; DR 생명주기
- 비영속(New/Transient): 영속성 컨텍스트와 전혀 연관되지 않은 상태.
- 영속(Managed): 영속성 컨텍스트에서 관리되는 상태.
- 준영속(Detached): 영속성 컨텍스트에서 분리된 상태.
- 삭제(Removed): 영속성 컨텍스트에서 삭제가 예약된 상태.
- 상태변화 메서드
- persist(): 엔티티를 영속성 컨텍스트에 저장.
- detach(): 엔티티를 준영속 상태로 전환.
- merge(): 준영속 상태의 엔티티를 다시 영속 상태로 병합.
- remove(): 엔티티를 삭제 상태로 전환.
참고
- https://hongchangsub.com/basicjpa2/
- 자바 ORM 표준 JPA 프로그래밍(김영한)
'Spring' 카테고리의 다른 글
JPA - JPQL (Java Persistence Query Language) (0) | 2025.01.15 |
---|---|
JPA - 연관관계 매핑 (0) | 2025.01.15 |
JPA - Entity 관련 애너테이션 (0) | 2025.01.14 |
JPA - @Column 데이터베이스 컬럼 매핑 (0) | 2025.01.14 |
JPA의 @GeneratedValue 전략 (0) | 2025.01.14 |