잘못된 정보를 발견하셨다면 지적과 수정요청 언제나 환영입니다 감사합니다 ! ! !
JPA/Hibernate 를 학습하다보면 영속성 컨텍스트(Persistence Context) 가 등장한다. 이것에 대해 알아보자.
0. 영속성 컨텍스트란 ?
먼저, 영속성 컨텍스트에 대한 정의는 java docs 의 Interface EntityManager 문서에서 찾아볼 수 있다.

음 ... 완벽하게 이해했어!
우선 드래그한 부분을 한글로 해석해보자.
영속성 컨텍스트는 영속(Persistent) 상태의 엔티티들이 관리되는 집합 공간이다.
영속성 컨텍스트 내에서는 각 엔티티의 고유한 식별자에 대해 단 하나의 엔티티 인스턴스만이 존재하며,
이 엔티티 인스턴스들과 그 생명 주기(Lifecycle)가 관리된다.
위 설명을 통해 아래의 2가지 정보를 알 수 있다. 이 정보들을 기준으로 영속성 컨텍스트에 대해 더 알아보자.
1. 영속성 컨텍스트는 엔티티의 '상태' 를 기준으로 관리여부를 결정한다.
2. 엔티티 인스턴스들의 생명주기를 관리하기 위해 영속성 컨텍스트가 제공하는 기능이 존재한다
1. 영속성 컨텍스트가 인식하는 엔티티의 상태 종류

아래 내용을 이해하기 위해서는 영속성 컨텍스트 내부의 1차 캐시에 대해 이해하고 있어야 한다.
* 영속성 컨텍스트의 1차 캐시
1차 캐시는 한 번 조회한 엔티티 인스턴스 정보를 임시로 저장해두는 공간이다. 1차 캐시가 없다면 같은 엔티티를 여러번 조회하더라도 매번 DB에 쿼리를 통해서만 얻을 수 있기 때문에, 1차 캐시는 엔티티의 효율적인 관리를 위해서 필수적이다.
엔티티 인스턴스는 Map 형태로 저장되는데,
Key = 엔티티의 @Id 값 / Value = 엔티티 인스턴스 그 자체 (new 를 통해 생성된 객체의 메모리 주소) 가 된다.
1차 캐시는 영속성 컨텍스트와 생명주기를 함께한다. Spring에서 주로 사용하는 Transaction Scoped 영속성 컨텍스트의 경우, 일반적으로 하나의 트랜잭션 범위와 동일하게 유지된다. 트랜잭션이 종료되거나 em.close() 를 실행하면 영속성 컨텍스트가 종료되고 1차 캐시도 함께 사라진다.
1. 비영속 / Transient(New)
객체를 생성만 했을 뿐 아직 영속성 컨텍스트에 의해 관리되지 않는 상태이다.
em.persist() 를 통해 영속(Managed) 상태로 만들 수 있다. (em 은 생성한 EntityManager 인스턴스)
2. 영속 / Managed(Associated)
EntityManager 에 의해 엔티티가 영속성 컨텍스트의 관리대상에 등록된 상태이다.
영속성 컨텍스트의 변경 감지(Dirty Checking) 기능이 동작한다.
3. 준영속 / Detached
ID는 할당이 되었지만, em.detach() 로 영속성 컨텍스트에서는 분리된 상태이다.
삭제와는 다르게 아직 ID가 살아있기 때문에, em.merge() 로 다시 영속 상태로의 전환이 가능하다.
(ID가 할당되었다는 말은, 일단 영속성 컨텍스트에 최소 한 번은 등록되었다는 뜻이다.)
준영속 상태가 되는 경우는 em.detach(), em.clear(), em.close() 호출 시이다.
4. 삭제 / Removed
em.remove() 를 통해 삭제 예정 상태로 마킹한다. DB에서 실제로 삭제되는 시점은 flush() 를 호출한 이후다.
flush()는 수동으로 호출하거나, 트랜잭션 커밋 시점에 자동으로 호출된다.
2. 영속성 컨텍스트가 제공하는 기능
1. 1차 캐시
앞서 설명한 기능이다. 영속성 컨텍스트 내부의 엔티티를 임시 저장소다.
1. em.find()를 통해 엔티티를 조회
2. 1차 캐시에서 해당 ID를 가진 엔티티가 있는지 조회
3. 만약 존재한다면 DB에 접근하지 않고, 메모리에서 바로 엔티티를 반환
위 과정을 통해 DB 접근이 아닌 메모리에서 바로 엔티티를 반환하여 조회 성능을 크게 향상 시킬 수 있다.
만약 조회하는 엔티티가 1차 캐시에 존재하지 않아서 DB를 조회했다면, 앞으로 1차 캐시를 활용할 수 있도록 해당 엔티티 정보를 1차 캐시에 저장한다. 1차 캐시는 세션 범위 내에서만 유효하다.
2. 동일성 보장
같은 트랜잭션 내에서 동일한 ID를 가진 엔티티를 여러 번 조회해도, 영속성 컨텍스트는 항상 최초에 조회된 동일한 메모리 주소의 인스턴스를 반환한다.
// memberA는 ID가 "user1"인 회원 엔티티
Member a = em.find(Member.class, "user1");
Member b = em.find(Member.class, "user1");
System.out.println(a == b); // true
이처럼 참조값을 비교하는 == 연산의 결과가 true로 나오는, 즉 '동일성'이 보장된다.
마치 자바 컬렉션에서 같은 객체를 여러 번 꺼내 쓰는 것처럼, 애플리케이션의 예측 가능하고 일관된 동작을 보장한다.
3. 변경 감지(Dirty Checking)
영속성 컨텍스트의 가장 강력한 기능 중 하나이다.
별도의 update 메서드를 호출하지 않아도, 엔티티의 변경 사항을 자동으로 감지하여 데이터베이스에 반영해준다.
1. JPA는 엔티티를 1차 캐시에 저장할 때, 해당 엔티티의 최초 상태를 복사하여 스냅샷(Snapshot)으로 저장해둔다.
2. 트랜잭션이 커밋(commit)되는 시점에, 1차 캐시에 있는 현재 엔티티와 이전에 만들어둔 스냅샷을 비교한다.
3. 만약 두 상태가 다르다면, JPA가 자동으로 UPDATE SQL을 생성하여 DB로 전송한다.
4. 쓰기 지연(Write Behind)
em.persist() 가 호출될 때 바로 DB에 INSERT SQL이 전송되지 않는다.
영속성 컨텍스트는 실행해야 할 쿼리들을 쓰기 지연 SQL 저장소에 모아두고, 트랜잭션 커밋 시점이 되어서야
모아두었던 SQL 쿼리들을 한꺼번에 DB로 전송한다. 이를 통해 불필요한 DB 접근 횟수를 줄일 수 있으며,
관련 설정을 추가하면 여러 SQL을 하나의 묶음으로 만들어 보내는 JDBC BATCH 기능을 활용한 성능 최적화가 가능하다. 변경 감지(Dirty Checking)에 의해 생성된 UPDATE 쿼리나, em.remove() 에 의해 생성된 DELETE 쿼리 역시 쓰기 지연 SQL 저장소에 저장되었다가 함께 처리된다.
5. 지연 로딩(Lazy Loading)
객체 그래프를 탐색할 때 성능을 최적화하기 위한 기능이다.
예를 들어 Member 엔티티가 Team 엔티티와 연관 관계를 맺고 있을 때, Member를 조회하는 시점에는 당장 필요 없는 Team 엔티티까지 함께 조회하는 것은 비효율적일 있는데, 지연 로딩을 통해 연관된 엔티티를 실제 사용하는 시점까지 DB 조회를 미룰 수 있다. 아래 박스를 통해 동작 과정을 파악하자.
1. Member 엔티티를 조회하면, Team 필드에는 실제 Team 객체 대신 Hibernate가 생성한 프록시(Proxy) 객체가 채워진다. 프록시는 실제 DB 조회가 일어나기 전까지는 초기화되지 않고, 필요할 때 쿼리를 실행하여 진짜 엔티티로 교체된다.
2. 이후 코드에서 member.getTeam().getName()처럼 Team 객체를 실제로 사용하는 시점에, JPA가 Team을 조회하는 SQL을 DB로 전송하여 진짜 객체로 바꿔치기한다.
이를 통해 불필요한 쿼리를 방지하고 애플리케이션의 전반적인 성능을 향상시킬 수 있다.
참고한 자료 (References)
[웹 문서 및 블로그]
Java 공식문서 (EntityManager Interface)
https://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html
Understanding EntityManager Sessions and First-Level Cache in Spring Data JPA (Ayoub seddiki)
Hibernate Caching Explained: First Level vs Second Level Cache (Gaddam.Naveen)
[인프런 강의]
김영한) 자바 ORM 표준 JPA 프로그래밍 - 기본편
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
[유튜브]
Spring boot : JPA (Part-3) | First Level Caching in JPA (Concept && Coding - by Shrayansh)