2026. 6. 27. 16:21ㆍ부트캠프/TIL(Today I Learned)
JPA 영속성 컨텍스트(Persistence Context) 완벽 이해
오늘은 Spring의 IoC/DI와 JPA의 기본 개념을 학습했고, 특히 JPA의 핵심이라고 할 수 있는 영속성 컨텍스트(Persistence Context)에 대해 깊게 공부했다.
IoC(Inversion of Control)
IoC는 객체의 생성과 생명주기 관리를 개발자가 직접 하지 않고 스프링 컨테이너가 담당하는 것을 의미한다.
기존 방식
public class OrderService {
private MemoService memoService = new MemoService();
}
객체를 직접 생성하고 관리한다.
IoC 적용
public class MemoController {
private final MemoService memoService;
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
}
객체 생성은 스프링 컨테이너가 담당하고 필요한 곳에 전달한다.
즉,
- 객체 생성 → 스프링
- 객체 사용 → 개발자
역할이 분리된다.
DI(Dependency Injection)
DI는 의존성 주입을 의미한다.
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
MemoController가 직접 MemoService를 생성하지 않고 외부에서 전달받는다.
장점
- 유지보수 용이
- 테스트 편리
- 결합도 감소
강한 결합과 약한 결합
강한 결합
public MemoController() {
this.memoService = new MemoService();
}
객체를 직접 생성한다.
문제점
- 구현체 변경 시 수정 범위 증가
- 단위 테스트 어려움
- 재사용성 감소
약한 결합
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
객체를 주입받는다.
장점
- 객체 교체 쉬움
- Mock 객체 사용 가능
- 테스트 용이
JPA란?
JPA(Java Persistence API)는 자바 ORM 기술의 표준이다.
ORM(Object Relational Mapping)
객체와 데이터베이스 테이블을 자동으로 매핑해주는 기술이다.
Java 객체
↓
JPA
↓
Database
대표적인 구현체
- Hibernate
- EclipseLink
실무에서는 Hibernate를 가장 많이 사용한다.
영속성 컨텍스트(Persistence Context)
영속성 컨텍스트란?
JPA가 Entity 객체를 효율적으로 관리하기 위해 사용하는 메모리 공간이다.
Application
↓
EntityManager
↓
Persistence Context
↓
Database
EntityManager를 통해 접근할 수 있다.
EntityManagerFactory와 EntityManager
먼저 JPA를 사용하려면 두 객체를 이해해야 한다.
EntityManagerFactory emf;
EntityManager em;
EntityManagerFactory 생성
// persistence.xml 설정 정보를 읽어온다.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("memo");
동작 과정
persistence.xml 읽기
↓
DB 연결 정보 확인
↓
Hibernate 초기화
↓
EntityManagerFactory 생성
특징
- 애플리케이션 전체에서 1개 사용
- 생성 비용이 매우 비쌈
- Thread Safe
EntityManager 생성
EntityManager em = emf.createEntityManager();
역할
Entity 저장
Entity 조회
Entity 수정
Entity 삭제
영속성 컨텍스트 관리
특징
- 요청마다 생성
- Thread Safe 하지 않음
테스트 환경 준비
public class EntityTest {
EntityManagerFactory emf;
EntityManager em;
@BeforeEach
void setUp() {
// persistence.xml 읽어서
// EntityManagerFactory 생성
emf = Persistence.createEntityManagerFactory("memo");
// 영속성 컨텍스트 관리자를 생성
em = emf.createEntityManager();
}
}
1차 캐시(First Level Cache)
영속성 컨텍스트 내부에는 1차 캐시가 존재한다.
구조
Map<PK, Entity>
예시
1L → Memo 객체
2L → Memo 객체
3L → Memo 객체
Entity 저장 과정
Memo memo = new Memo();
// PK 설정
memo.setId(1L);
// 사용자명 설정
memo.setUsername("Robbie");
// 내용 설정
memo.setContents("영속성 컨텍스트");
아직 DB에 저장되지 않았다.
단순히 자바 객체 생성만 된 상태다.
persist()
em.persist(memo);
내부 동작
persist()
↓
1차 캐시에 저장
↓
쓰기 지연 저장소에 INSERT SQL 저장
↓
DB 반영 X
그림으로 표현하면
영속성 컨텍스트
1차 캐시
-------------------
1L → Memo
-------------------
쓰기 지연 저장소
-------------------
INSERT INTO memo ...
-------------------
여전히 DB에는 저장되지 않았다.
트랜잭션 시작
EntityTransaction tx = em.getTransaction();
의미
EntityManager로부터 트랜잭션 객체를 가져온다.
tx.begin();
의미
트랜잭션 시작
이 시점부터
모든 작업은
하나의 작업 단위로 묶인다.
저장 성공 예제
@Test
void saveTest() {
// 트랜잭션 객체 획득
EntityTransaction tx = em.getTransaction();
// 트랜잭션 시작
tx.begin();
try {
// 새로운 엔티티 생성
Memo memo = new Memo();
// PK 지정
memo.setId(1L);
// username 저장
memo.setUsername("Robbie");
// 내용 저장
memo.setContents("Persistence Context");
// 영속성 컨텍스트에 저장
em.persist(memo);
// commit 수행
tx.commit();
} catch (Exception e) {
// 오류 발생 시 롤백
tx.rollback();
} finally {
// EntityManager 종료
em.close();
}
}
Commit 시점
가장 중요하다.
tx.commit();
실행 순간
1차 캐시
↓
쓰기 지연 저장소 확인
↓
INSERT SQL 실행
↓
DB 반영
즉
em.persist(memo);
가 아니라
tx.commit();
할 때 DB에 실제 저장된다.
Entity 조회
Memo memo = em.find(Memo.class, 1L);
캐시에 없는 경우
find()
↓
1차 캐시 조회
↓
없음
↓
DB 조회
↓
캐시에 저장
↓
반환
실행 SQL
select *
from memo
where id = 1;
캐시에 있는 경우
Memo memo1 = em.find(Memo.class, 1L);
Memo memo2 = em.find(Memo.class, 1L);
실행 과정
첫 번째 조회
↓
DB 조회
두 번째 조회
↓
1차 캐시 조회
SQL은 한 번만 실행된다.
객체 동일성 보장
Memo memo1 = em.find(Memo.class, 1L);
Memo memo2 = em.find(Memo.class, 1L);
System.out.println(memo1 == memo2);
결과
true
왜?
1차 캐시에
1L → Memo 객체
하나만 존재
같은 객체 참조 반환
JPA가 동일성을 보장해준다.
Dirty Checking(변경 감지)
실무에서 엄청 중요하다.
tx.begin();
Memo memo = em.find(Memo.class, 1L);
memo.setContents("수정된 내용");
tx.commit();
여기서 UPDATE SQL이 없다.
그런데 DB는 수정된다.
왜?
동작 원리
조회 시
현재 상태 저장
username=Robbie
contents=기존 내용
commit 시
현재 상태 비교
기존 내용
↓
수정된 내용 발견
자동으로
update memo
set contents = ?
where id = ?
생성된다.
이를 Dirty Checking이라고 한다.
Entity 삭제
tx.begin();
Memo memo = em.find(Memo.class, 1L);
em.remove(memo);
tx.commit();
동작
MANAGED 상태
↓
REMOVE 상태
↓
commit
↓
DELETE SQL 실행
실행 SQL
delete
from memo
where id = 1;
영속성 컨텍스트 핵심 정리
- EntityManager를 통해 영속성 컨텍스트에 접근한다.
- 영속성 컨텍스트 내부에는 1차 캐시가 존재한다.
- persist()는 DB 저장이 아니라 영속성 컨텍스트 저장이다.
- 실제 SQL은 commit 시점에 실행된다.
- 같은 엔티티 조회 시 동일한 객체를 반환한다.
- 변경 감지(Dirty Checking)를 통해 UPDATE SQL을 자동 생성한다.
- JPA 성능 최적화의 핵심은 영속성 컨텍스트를 이해하는 것이다.
오늘 느낀 점
JPA를 처음 공부할 때는 단순히 SQL을 대신 작성해주는 기술이라고 생각했다.
하지만 영속성 컨텍스트를 학습하면서 JPA의 핵심은 SQL 자동 생성이 아니라 Entity를 관리하는 방식에 있다는 것을 알게 되었다.
특히 1차 캐시, 쓰기 지연 저장소, 변경 감지(Dirty Checking), 동일성 보장 개념은 앞으로 JPA를 사용할 때 반드시 이해하고 있어야 할 핵심 개념이라고 느꼈다.
'부트캠프 > TIL(Today I Learned)' 카테고리의 다른 글
| [내일배움캠프_단기심화_8기]_Days-7-TIL (0) | 2026.06.30 |
|---|---|
| [내일배움캠프_단기심화_8기]_Days-6-TIL (0) | 2026.06.29 |
| [내일배움캠프_단기심화_8기]_Days-5-TIL (0) | 2026.06.26 |
| [내일배움캠프_단기심화_8기] Days 4 TIL (Today I Learned) (0) | 2026.06.25 |
| [내일배움캠프 사전캠프_단기심화_8기]_Day 3 TIL (0) | 2026.06.24 |