[스프링부트 #15] JPA Persistence Context

도경원's avatar
Aug 25, 2025
[스프링부트 #15] JPA Persistence Context

1. Persistence Context란 ?

영속성 컨텍스트(PC)는 엔티티 객체를 저장하고 관리하는 메모리 상의 저장소이다.
쉽게 말해, DB에 저장될 객체들을 1차로 보관하고 추적하는 일종의 캐시 역할을 한다. EntityManager가 생성할 때 같이 만들어지고, 트랜잭션 범위 내에서 살아있다. 영속화된 객체는 PC 안에 들어가며, 변경 사항은 자동으로 추적된다.

2. 왜 “Context” 인가 ?

프로그래밍에서 “Context”는 어떤 객체의 상태를 알고 추적할 수 있는 범위를 의미한다.
PC는 자바 객체(Entity)와 DB row를 연결하는 중간 지점이다. 객체의 변경 상태를 기억하고, DB와 싱크를 맞추는 책임을 가진다.
 

3. Persistence Context가 하는 일

  • 1차 캐시 : 동일 트랜잭션 내에서 같은 ID로 조회하면 DB 접근 없이 캐시에서 꺼낸다.
캐싱 : 조금 더 가까운데서 가져올 수 있는것.
  • 변경 감지 (Dirty Checking) : 객체 필드 값이 바뀌면 트랜잭션 종료 시 자동으로 update 쿼리 발생한다.
  • 쓰기 지연(SQL 저장소) : 트랜잭션 내에서 persist() 시 실제 DB 반영을 나중에 몰아서 처리한다.
  • 플러시(Flush) : 변경 내용을 DB에 반영하는 시점 (자동 or 수동)

4. 영속화의 3단계 이해

상태
설명
비영속(new)
그냥 new한 객체. 아직 DB와 무관
영속
em.persist() 호출 시 PC에 등록되어 DB와 연결됨
준영속/삭제
em.detach(), em.remove() 시 PC에서 빠짐

5. 영속화 방법

방법 1 : em.persist()

User user = new User(...); em.persist(user); // → 영속 상태로 진입

방법 2: JPQL 쿼리 결과

User user = em.createQuery("select u from User u", User.class).getSingleResult();
💬
JPQL로 조회된 객체도 자동으로 영속 상태
new User()만 한 객체는 PC에 등록되지 않음 → 비영속 상태

6. em.find() 의 동작 방식

User u1 = em.find(User.class, 1L); User u2 = em.find(User.class, 1L);
첫 번째 호출하면 DB를 조회하고 PC에 저장하지만, 두 번째 호출은 DB 접근 없이 PC 캐시에서 조회한다.
즉, 1차 캐시 기능을 보여준다.
em.find() 는 오직 PK 기반으로만 조회 가능하다. 이름(username)으로 찾고 싶으면 JPQL을 사용해야 한다.

7. 지연로딩(LAZY), 즉시로딩(EAGER)

@ManyToOne(fetch = FetchType.LAZY)

연관된 엔티티를 즉시 가져오지 않고, 필요할 때 (getUser())로 조회한다. 쿼리 최적화 가능하다. (N + 1 문제는 주의하자)

@ManyToOne(fetch = FetchType.EAGER)

연관된 엔티티를 즉시 조회한다. 쿼리가 무조건 JOIN을 날리므로 유연성이 떨어지고 성능 저하 가능성이 있다.
LAZY는 필요할때 가져오는 느낌이고 EAGER은 미리 다 준비해놓는 방법이다. 실무에서는 무조건 LAZY + fetchjoin(JPQL) 전략이 권장된다.

예시 코드 )

DB 상태 (샘플 데이터)
insert into user_tb(username, password, email) values ('ssar', '1234', 'ssar@nate.com'); -- id=1 insert into user_tb(username, password, email) values ('cos', '1234', 'cos@nate.com'); -- id=2 insert into board_tb(title, content, user_id) values ('제목1','내용1',1); -- id=1 insert into board_tb(title, content, user_id) values ('제목2','내용2',1); -- id=2 insert into board_tb(title, content, user_id) values ('제목3','내용3',1); -- id=3 insert into board_tb(title, content, user_id) values ('제목4','내용4',1); -- id=4 insert into board_tb(title, content, user_id) values ('제목5','내용5',2); -- id=5
notion image
notion image
@ManyToOne(fetch = FetchType.LAZY) 일때
총 쿼리 수는 2번이다. (Board 1회 + 필요한 순간의 User 1회) @ManyToOne(fetch = FetchType.EAGER)
총 쿼리 수는 3번이다. (Board 1회 + 고유한 user 수 만큼 (샘플 데이터 상 2회)) 총 3회이다.
 

8. 캐싱 개념과 Persistence Context의 관계

캐시는 (상대적으로) 가까운 곳에서 데이터를 꺼낸다는 개념이다. 제일 가깝지 않아도 됨.
PC는 1차 캐시 역할을 하고 DB보다 빠른 조회가 가능하고, 중복 쿼리 방지한다. 변경 추척까지 포함된 지능형 캐시라고 보면된다.

예시 코드

notion image
notion image
첫 호출 findById(1)
  • DB 조회 실행 → 결과 엔티티를 **영속성 컨텍스트(1차 캐시)**에 저장.
두 번째 호출 findById(1)
  • 같은 트랜잭션/같은 EntityManager라면 DB를 다시 가지 않고 1차 캐시에서 바로 반환.
즉, 동일 PK에 대한 두 번째 find는 캐시 히트 → SQL 1번만 발생.

마무리

마무리 하자면 Persistence Context는 JPA가 객체를 관리하고 DB와 동기화하기 위한 핵심 기술이다. 캐싱 + 변경감지 + 쓰기지연 + 트랜잭션 자동 관리까지 도맡은 강력한 구조라고 볼 수 있다.
 
Share article

Gyeongwon's blog