2025. 10. 20. 11:32ㆍJava/JPA
JPA 영속성 컨텍스트와 1차 캐시 완전 정리
이번 실습은 persist() 호출 시점과 실제 INSERT SQL 실행 시점의 차이를 이해하기 위한 핵심 주제다.
즉, “JPA가 객체를 어떻게 관리하고 언제 DB에 반영하는가”를 실습으로 확인한다.
❶ 프로젝트 구성
src/main/java/entity/
├─ Product.java
└─ Test.java
src/main/resources/META-INF/
└─ persistence.xml
📄 Product.java
@Entity
public class Product {
@Id
private int id;
private String name;
// getter/setter
}
📄 Test.java
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class Test {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Product p1 = new Product();
p1.setId(1);
p1.setName("Book");
em.persist(p1); // 아직 DB 반영 X (1차 캐시에 저장만)
Product p2 = new Product();
p2.setId(2);
p2.setName("Phone");
em.persist(p2);
em.getTransaction().commit(); // commit 시 flush() → 실제 INSERT SQL 실행
em.close();
}
}
📄 persistence.xml
<persistence-unit name="my-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.cj.jdbc.Driver"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa_basic_1"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="root"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
❷ 실행 결과 분석
실행 직후 콘솔에는 다음처럼 SQL이 한 번에 출력된다.
Hibernate: insert into product (name, id) values (?, ?)
Hibernate: insert into product (name, id) values (?, ?)
이게 바로 commit() 시점에 flush()가 자동 호출되면서 1차 캐시에 있던 엔티티들이 DB에 반영된 것이다.
❸ 영속성 컨텍스트란?
JPA가 엔티티를 관리하는 가상의 메모리 공간이다.
이 안에는 모든 엔티티 객체가 저장되고, JPA는 이 상태를 추적한다.
쉽게 말해, “DB에 넣기 전 임시저장소”라고 생각하면 된다.
여기서 모든 엔티티는 1차 캐시라는 Map 구조로 관리된다.
영속성 컨텍스트 내부 구조
┌──────────────────────────────┐
│ 1차 캐시 (Map<@Id, Entity>) │
│ 1 → Product(id=1, name=Book) │
│ 2 → Product(id=2, name=Phone)│
└──────────────────────────────┘
❹ persist() → flush() → commit() 흐름
아래는 persist() 이후 INSERT SQL이 실행되는 정확한 시점을 시각적으로 표현한 것이다.
즉, persist()는 영속성 컨텍스트에 등록하는 명령일 뿐, 실제 SQL은 flush() → commit() 시점에 실행된다.

❺ 1차 캐시의 개념
영속성 컨텍스트 내부에는 1차 캐시가 존재한다.
이곳에는 @Id 값을 key로, 엔티티 객체가 저장된다.
따라서 같은 엔티티를 여러 번 조회해도 DB 접근이 일어나지 않는다.
Product p1 = em.find(Product.class, 1); // DB 조회 → 1차 캐시 저장
Product p2 = em.find(Product.class, 1); // 1차 캐시에서 반환 (SQL X)
System.out.println(p1 == p2); // true
➡️ 즉, 동일 트랜잭션 내에서는 같은 엔티티는 항상 동일 객체로 보장된다.
❻ Dirty Checking (변경 감지)
영속 상태의 엔티티는 JPA가 지속적으로 감시한다.
commit 시점에 값이 변경되면 자동으로 UPDATE SQL이 발생한다.
Product p = em.find(Product.class, 1);
p.setName("Laptop"); // 변경만 해도
em.getTransaction().commit(); // 자동 update SQL 생성
Hibernate: update product set name=? where id=?
❼ 정리 요약
| 상태 | 설명 | SQL 실행 |
|---|---|---|
| 비영속 (Transient) | new로 생성된 객체 | X |
| 영속 (Managed) | persist()로 컨텍스트에 등록 | commit 시 flush 후 실행 |
| 준영속 (Detached) | em.detach()로 관리 해제 | X |
| 삭제 (Removed) | remove()로 삭제 예약 | commit 시 DELETE 실행 |
❽ 핵심 한 줄 요약
persist()는 DB insert가 아니라
“엔티티를 영속성 컨텍스트의 1차 캐시에 등록”하는 명령이다.
실제 SQL은 flush() 또는 commit() 시점에 실행된다.
📚 용어 정리
| 용어 | 설명 |
|---|---|
| 영속성 컨텍스트 (Persistence Context) | 엔티티를 저장하고 관리하는 메모리 공간 |
| 1차 캐시 (First Level Cache) | 영속성 컨텍스트 내부의 엔티티 저장소 (Map 구조) |
| flush() | 1차 캐시의 변경 내용을 DB에 반영 |
| commit() | flush()를 자동 호출하고 트랜잭션 종료 |
| Dirty Checking | 엔티티 변경 감지 후 자동 update SQL 생성 |
'Java > JPA' 카테고리의 다른 글
| [LG U+ 유레카 3기] Spring Data JPA CRUD + Lombok + 패턴 실습 정리 (0) | 2025.11.25 |
|---|---|
| [LG U+ 유레카 3기] JPA N+1 · Fetch Join · JPQL Join (0) | 2025.11.20 |
| [LG U+ 유레카 3기] 순수 JPA 복합키 실습(@IdClass & @EmbeddedId) (1) | 2025.11.17 |
| [LG U+ 유레카 3기]순수JPA 실습 — JPQL (Java Persistence Query Language) (0) | 2025.10.20 |
| [LG U+ 유레카 3기]순수 JPA 실습 엔티티 생명주기 (persist / find / merge / detach / remove) (0) | 2025.10.20 |