JPA 영속성 컨텍스트은 또 뭐야?

이성민·2025년 11월 14일

woowacourse

목록 보기
9/12
post-thumbnail

이 글을 쓰게 된 이유

이 글은 JPA 연관관계가 뭐야?! 라는 질문에서 출발했다.

우테코 오픈 미션을 진행하면서 JPA 연관관계를 공부하던 중 “엔티티끼리는 분명히 참조로 연결해두었는데 이게 정확히 어떤 시점에 DB에 반영되는 걸까?” 라는 궁금증이 생겼다.

특히 연관관계를 설정할 때 “JPA는 어떤 기준으로 엔티티의 상태를 추적하고, 언제 DB에 쓰는 걸까?” 이 부분이 명확히 이해되지 않았다.

이 궁금증을 따라가다 보니 자연스럽게
영속성 컨텍스트(Persistence Context) 라는 개념과 마주하게 됐다.

처음에는 단순히 외워야 할 개념 정도로 생각했지만
공부할수록 이 개념이 JPA의 근본적인 동작 원리를 이해하는 핵심이라는 걸 깨달았다.

그래서 이 글은 “JPA가 어떻게 엔티티를 관리하며, 왜 그렇게 동작하는가”를 한눈에 볼 수 있도록 정리한 내용이다.

미래의 내가 다시 돌아왔을 때 “아, 그래서 JPA가 이렇게 작동하는 거였지”하고 바로 이해할 수 있도록 나를 위한 복습용 아카이브이기도 하다.


JPA 영속성 컨텍스트란?

JPA의 모든 동작은 영속성 컨텍스트(Persistence Context)를 중심으로 이루어진다.
이 컨텍스트는 말 그대로 엔티티를 “영속” 상태로 관리하는 메모리 공간이다.
즉, 엔티티를 단순히 생성하는 것이 아니라, JPA가 해당 객체를 추적하고 변경 사항을 관리하도록 등록하는 영역이다.


엔티티 생명주기 4단계

JPA의 엔티티는 NewManagedDetachedRemoved 네 가지 상태를 가진다.
이 그림은 그 상태들이 어떤 메서드에 의해 이동하는지 그리고 JPA 내부에서 어떤 일이 일어나는지를 보여준다.

New(비영속 상태)

  • 단순히 new로 생성된 객체
  • 아직 영속성 컨텍스트에 등록되지 않은 상태
  • JPA가 관리하지 않는다 → DB와 전혀 관련 없음
Book book = new Book("자바의 정석", "남궁성");

- 내부 동작

  • 메모리 안에만 존재
  • EntityManager는 이 객체를 전혀 모름
  • flush()commit()과 무관

new로 만든 객체는 JPA 입장에서 그냥 일반 자바 객체일 뿐이다.

영속(Managed)

em.persist(book) 을 호출하면 비영속 → 영속 상태로 전환된다.
즉, 이제 JPA가 이 객체를 영속성 컨텍스트(1차 캐시) 안에서 관리한다.

em.persist(book);

- 내부 동작

  • JPA가 book을 1차 캐시(Map 구조) 에 등록
  • @Id(PK) 값을 키로 관리
  • flush() 시점에만 DB에 실제 INSERT 실행
  • 이후부터 JPA는 book의 필드 변경을 감시(Dirty Checking)
book.setTitle("자바의 정석 - 개정판"); // 변경만 해도 JPA가 감지
em.flush(); // UPDATE SQL 자동 실행

영속 상태가 되면 엔티티의 변경이 SQL로 자동 반영된다.

Detached(준영속 상태)

detach(), clear(), close()를 호출하면 영속성 컨텍스트에서 엔티티가 제거되어 더 이상 JPA가 관리하지 않는다.

em.detach(book); // 개별 엔티티 분리

- 내부 동작

  • 1차 캐시에서 제거됨
  • 변경 감지(Dirty Checking) 중단
  • 이후에 필드를 수정해도 DB에는 반영되지 않음

Detached는 말 그대로 “영속성 컨텍스트와의 연결이 끊어진 상태”다.

다시 영속 상태로 복구하려면 merge()를 사용한다.

Book mergedBook = em.merge(book); // 준영속 → 영속

merge()는 기존 객체를 다시 관리 상태로 복구하지만 내부적으로는 새로운 영속 객체를 만들어 반환한다.

Removed(삭제 상태)

em.remove(book)을 호출하면 엔티티가 삭제 예약 상태가 된다.
즉시 삭제되지 않고, flush 시점에 DELETE SQL이 실행된다.

em.remove(book);

- 내부 동작

  • 영속성 컨텍스트에서 해당 객체에 “삭제 플래그”를 표시
  • flush() 또는 commit() 시 실제 DELETE FROM book ... 쿼리 실행
  • 삭제 후 1차 캐시에서도 제거

remove()는 즉시 DB에서 지워지는 게 아니라 트랜잭션 커밋 시점에 반영된다.

flush()와 DB의 관계

그림에서 보듯 Managed 상태의 엔티티만이 DB와 직접적인 연결을 가진다.

JPA는 INSERT, UPDATE, DELETE 쿼리를 즉시 보내지 않고 '쓰기 지연 SQL 저장소(Write-behind)'라는 공간에 모아둔다.

em.persist(bookA);
em.persist(bookB);
// 아직 INSERT 없음

em.flush(); // INSERT 2개 SQL 한 번에 실행
em.commit(); // 트랜잭션 커밋
  • flush() = 영속성 컨텍스트 ↔ DB 상태 동기화
  • commit() = flush() + 실제 트랜잭션 종료

그럼 스프링에서 persist는 언제 호출 되는가?

영속성 컨텍스트를 이해하려면 하나 더 중요한 사실이 있다.
스프링 환경에서 우리가 흔히 사용하는

bookRepository.save(book);

이 코드는 내부적으로 JPA의 EntityManager.persist(book) 또는 merge(book)를 호출한다.

  • 새 엔티티 → persist()
  • 기존 엔티티 → merge()

즉, save()가 호출되는 시점이 엔티티가 비로소 영속(Managed) 상태가 되는 순간이다.


마무리

나는 예전에는 코드가 돌아가기만 하면 된다고 생각했었다.
하지만 이제는 “왜 그렇게 동작하는가?, 그 안에서 어떤 원리가 작동하는가?”를 먼저 질문하게 되었다.

이 경험을 통해 우아한테크코스가 추구하는 이해를 기반으로 한 성장이라는 가치가 실제로 어떤 의미인지 직접 경험하게 되었다.

이제는 단순히 기능을 구현하는 개발자가 아니라 기능 뒤의 원리까지 스스로 설명할 수 있는 개발자로 성장하고 싶다는 마음이 생겼다.

참조

스프링 영속성 관리 (영속성 컨텍스트)
JPA 영속성 컨텍스트란?
[JPA] 영속성 컨텍스트(Persistence Context)란? - 개넘 정리 및 사용법

profile
BE 개발자

0개의 댓글