
1. 들어가며
스프링 + JPA를 공부하다 보면 꼭 듣게 되는 단어가 있다.
바로 “영속성 컨텍스트 (Persistence Context)”
“엔티티가 영속성 컨텍스트에 저장됩니다.”
“영속 상태로 관리됩니다.”
“flush가 일어납니다.”
이런 말을 듣고 나면 솔직히 이런 생각이 들었다..
“DB에 저장만 하면 되는 거 아닌가?”
“굳이 중간에 ‘영속성 컨텍스트’라는 게 왜 필요한 거야?”
나도 처음엔 그랬다.
new로 객체를 만들고, save() 해서 DB에 넣으면 끝 아닌가?
왜 굳이 중간에 “영속성 컨텍스트”라는 걸 거쳐야 하는지 이해가 안 됐다.
근데 이걸 이해하게 되면,
JPA의 진짜 장점(자동 업데이트, 캐시, 트랜잭션 일관성)이 눈에 보이기 시작한다.
즉, “영속성 컨텍스트”는 JPA를 단순 SQL 매퍼가 아닌 ‘객체를 관리하는 ORM’으로 만들어주는 핵심이다.
2. 영속성 컨텍스트란?
한 문장으로 말하면,
“엔티티를 저장하고, 관리하고, 추적하는 JPA 내부의 메모리 공간” 이다.
조금 더 쉽게 말하면 “엔티티 임시 저장소”라고 볼 수 있다.
우리가 엔티티를 persist() 하면,
DB에 바로 저장되는 게 아니라,
일단 영속성 컨텍스트 안의 1차 캐시에 저장된다.
Member member = new Member("yea_hyun");
entityManager.persist(member);
이때 JPA는 이렇게 행동한다.
1) DB에 바로 INSERT하지 않는다.
2) 대신 “영속성 컨텍스트” 안에 id=1, name='yea_hyun' 형태로 저장한다.
3) 나중에 트랜잭션이 커밋(commit) 되는 순간, JPA가 알아서 DB에 INSERT 쿼리를 날린다.
즉, JPA는
DB에 바로 접근하지 않고, 영속성 컨텍스트라는 중간 메모리 공간에서 엔티티를 관리하는 구조 를 가지고 있는 것이다.
3. 영속성 컨텍스트가 하는 일
영속성 컨텍스트는 단순히 데이터를 보관하는 공간이 아니다.
엔티티의 상태를 추적하고, 변경을 감지하고, 효율적으로 DB와 통신하도록 돕는다.
그 기능을 하나씩 살펴보자
1) 1차 캐시 (First-Level Cache)
영속성 컨텍스트는 1차 캐시 역할을 한다. 즉, 한 번 조회한 엔티티는 다시 DB를 가지 않고, 메모리에서 꺼내 쓴다.
Member m1 = em.find(Member.class, 1L);
Member m2 = em.find(Member.class, 1L);
여기서 SQL SELECT는 한 번만 실행된다.
두 번째 조회 때는 DB를 가지 않고,
이미 1차 캐시에 저장된 엔티티를 그대로 반환하기 때문이다.
이게 왜 중요하냐면 DB에 접근하는 건 항상 비용이 크다. 즉, 영속성 컨텍스트 덕분에 같은 트랜잭션 안에서는 중복 조회 없이 성능이 훨씬 빨라진다.
2) 동일성 보장 (Identity Guarantee)
영속성 컨텍스트는 동일한 엔티티는 하나의 인스턴스만 관리한다.
Member a = em.find(Member.class, 1L);
Member b = em.find(Member.class, 1L);
System.out.println(a == b); // true
a, b 둘 다 같은 객체를 참조하고 있다. 이걸 “동일성 보장”이라고 한다. 즉, 같은 트랜잭션 내에서는 같은 데이터는 하나의 객체로만 존재한다.
이렇게 되면 좋은 점:
- 객체의 변경이 곧 DB 반영으로 이어질 수 있다.
- 데이터의 일관성이 유지된다.
3) 변경 감지 (Dirty Checking)
이건 JPA의 핵심 중 핵심이다. 영속성 컨텍스트는 엔티티가 처음 저장될 때의 “스냅샷”을 보관한다. 그 후 트랜잭션이 끝나기 전에
현재 엔티티의 상태를 “스냅샷”과 비교해서 달라진 부분이 있다면 자동으로 UPDATE 쿼리를 날린다.
Member m = em.find(Member.class, 1L);
m.setName("예현"); // update() 안 해도 자동 반영됨
우리가 따로 update()나 save()를 호출하지 않아도 된다.
그냥 객체의 필드만 수정해도,
트랜잭션 커밋 시점에 자동으로 UPDATE 쿼리가 나간다.
즉, JPA는 “객체의 변경을 감지해서 DB에 반영하는 시스템”이다.
이걸 가능하게 하는 게 바로 영속성 컨텍스트다.
4) 쓰기 지연 (Write-Behind)
persist()를 호출한다고 해서 바로 DB에 INSERT 되는 건 아니다.
em.persist(member1);
em.persist(member2);
System.out.println("아직 INSERT 쿼리 안 나감!");
JPA는 쓰기 지연 SQL 저장소에 쿼리를 모아둔다.
그리고 트랜잭션이 커밋될 때 한 번에 INSERT 쿼리를 몰아서 실행한다.
이게 바로 “쓰기 지연”이다.
즉, DB 부하를 줄이고, 효율적으로 SQL을 처리할 수 있다.
4. 플러시(Flush)란?
flush는 영속성 컨텍스트의 변경사항을 DB에 반영하는 시점이다. 다만, flush는 캐시를 비우는 게 아니다.
단순히 “DB와 동기화만 시켜주는 과정”이다. 다음 세 가지 경우에 flush가 자동으로 일어난다.
1) 트랜잭션이 커밋될 때
2) JPQL이 실행되기 직전
3) 직접 em.flush()를 호출했을 때
즉, 영속성 컨텍스트는 평소엔 캐시처럼 있다가,
“이제 실제로 DB 반영할 타이밍이다!” 싶을 때 flush를 실행한다.
5. 영속 상태와 비영속 상태
영속성 컨텍스트는 엔티티를 4가지 상태로 구분한다.
| 상태 | 설명 |
| 비영속 (new) | 아직 영속성 컨텍스트에 저장되지 않은 상태 (new로만 만든 객체) |
| 영속 (managed) | persist()를 호출해서 JPA가 관리 중인 상태 |
| 준영속 (detached) | detach()나 clear() 등으로 컨텍스트에서 분리된 상태 |
| 삭제 (removed) | remove()가 호출된 상태 |
Member m = new Member("yea_hyun"); // 비영속
em.persist(m); // 영속
em.detach(m); // 준영속
em.remove(m); // 삭제
이렇게 “엔티티의 생애주기”를 관리하는 것도
전부 영속성 컨텍스트의 역할이다.
6. 영속성 컨텍스트가 없다면?
만약 영속성 컨텍스트가 없다면 어떤 일이 생길까?
- 매번 DB에서 직접 데이터를 불러와야 한다.
- 같은 엔티티를 불러도 매번 다른 객체로 인식된다.
- 객체 수정 후 UPDATE 쿼리를 직접 써야 한다.
- 트랜잭션 단위의 데이터 일관성이 깨진다.
즉, JPA는 단순히 SQL을 덜 쓰게 하는 도구가 아니라,
“객체의 생명주기를 관리해서 개발자가 SQL을 의식하지 않게 해주는 도구”다.
7. 정리
| 개념 | 설명 |
| 영속성 컨텍스트 | 엔티티를 저장하고 관리하는 1차 캐시 |
| 핵심 기능 | 1차 캐시, 동일성 보장, 변경 감지, 쓰기 지연 |
| flush 시점 | 트랜잭션 커밋, JPQL 실행 전, 직접 호출 시 |
| 엔티티 생애주기 | 비영속 → 영속 → 준영속 → 삭제 |
| 이점 | DB 접근 최소화, 자동 update, 트랜잭션 일관성 |
결국 한 문장으로 정리하면 이렇다.
“영속성 컨텍스트는 JPA가 객체를 ‘상태’로 관리할 수 있게 해주는 핵심 시스템이다.”
“이걸 이해하면, JPA가 왜 단순 CRUD 라이브러리가 아닌지 명확히 보인다.”
'JAVASPRING > study' 카테고리의 다른 글
| Spring Bean — 왜 만들어야 하고, 왜 써야 할까? (0) | 2025.10.07 |
|---|---|
| N+1 문제 (3) | 2025.09.26 |
| Proxy 객체란? (1) | 2025.09.23 |
| H2 database, 개념 잡기 (0) | 2024.06.27 |
| GlobalException (0) | 2024.06.21 |