영속성 전이 전에, 영속성 컨텍스트부터( 영속성 컨텍스트 )

KUN·2025년 6월 24일

계기

Cascade 나 OrphanRemoval 영속성 전이를 배우기 전에 꼭 영속성 컨텍스트라는 단어가 나왔다.
Cascade 하고 OrphanRemoval 는 아는데, 대체 영속성 컨텍스트가 뭔지 정확히 알고 싶어졌다.

목표

  • 영속성 컨텍스트란
  • 영속성 컨텍스트 범위
  • 영속성 컨텍스트 상태
  • 영속성 등록방법?

영속성 컨텍스트란?

개념

엔티티 객체를 저장하고 관리하는 메모리 공간, 즉 JPA가 엔티티를 관리하는 1차 캐시이다.

JPA 내부 기능이다.

추가설명( EntityManger - Oracle Docs )

영속성 컨텍스트는 EntityManager에 의해 관리되며,  
하나의 식별자(ID)에 대해 하나의 엔티티 인스턴스만 존재하도록 보장하고,  
그 생명주기를 관리하는 메모리 내 저장소이다.

JPA에서 데이터를 DB에서 꺼내오거나 저장할 때,
직접 DB에 접근하는 것이 아니라, 먼저 영속성 컨텍스트에 등록하여 관리된다.

이 컨텍스트에 등록된 객체를 영속 상태라고 하며,
영속성 컨텍스트는 보통 EntityManager 인스턴스당 하나씩 존재한다.

비유하면 다음과 같다.

학교 시절을 생각해보자.

출석부(영속성 컨텍스트)에는 모든 학생들(엔티티)의 정보가 적혀 있다.
선생님(EntityManager)은 이 출석부를 가지고 학생들을 관리한다.

만약 "김철수"라는 학생을 처음 본다면,
선생님은 출석부에 새로 등록한다 // em.persist(철수)

이미 출석부에 있는 학생이면,
새로 등록하지 않고 기존 정보를 계속 사용한다 // 동일성(identity) 보장

학생이 지각을 했다면, 선생님은 바로 행정실(DB)에 보고하지 않고
출석부에만 적어두었다가 나중에 한 번에 보고한다 // 변경 감지 + 지연 쓰기

하루 수업이 끝나고 선생님은 출석부를 행정실에 제출한다 // flush / commit

이처럼 출석부는 '현재 반에서 관리 중인 학생들'을 추적하고 기록하는 도구다.
바로 이 역할이 JPA에서의 영속성 컨텍스트다.

영속성 컨텍스트 범위

기본적으로 총 2개의 범위가 존재한다.
1. 트랜잭션 범위 ( 기본값 )
2. 확장 범위 ( 선택값 )

요약된 내용 ( 스포 )

일반적인 웹/서비스 환경에서는 트랜잭션 범위가 안전하고 효율적이며,
확장 범위는 상태 유지가 필요한 특수한 경우에만 신중하게 사용해야 한다.


트랜잭션*범위(기본)트랜잭션*범위(기본)

트랜잭션이 시작될 때 생성되고, 끝나면 사라진다.

비유: 한 번 수업하고 나면 버려지는 임시 출석부 같은 느낌이다.

- 수업 시작 = 트랜잭션 시작
- 수업 종료 = 트랜잭션 종료 (출석부 폐기)

출석부는 수업 시간 동안만 사용되며,  
수업이 끝나면 관리 중이던 학생들도 추적이 끊긴다. // Detached 상태

다음 수업에서는 새로운 출석부가 만들어지고,  
같은 학생이라도 새로 기록해야 동일한 추적이 가능하다. // 동일성 보장 안 됨

학생이 수업 중에 정보를 수정하더라도,  
선생님은 메모만 해두고 수업 끝날 때 한꺼번에 행정실에 보고한다. // (Dirty Checking + flush)

오해할 수 있는 부분 Check

트랜잭션에서도 그럼 선생님은 항상 같으니까 EntityManager 는 항상 같은 건가?
 └ 트랜잭션 범위에서는 EntityManager 가 임시 선생님으로 배정 된다.
     즉, 수업을 할 때 마다 다른 선생님이 들어오게 된다.

장점

  • 단순한 생명 주기 ( 트랜잭션 시작 ~ 종료 까지만 운영되니까 )
  • 리소스 관리 용이 ( 트랜잭션 끝나면 삭제하니까 리소스가 적음 )
  • 멀티스레드 안전 ( 스레드 단위로 처리 하기 때문에 )
  • 스프링과 잘 맞음 ( 요청 - 응답 이 1:1 구조이기 때문에 )

단점

  • 상태 유지 어려움 ( 여러 환경에도 똑같은 객체를 유지하기 어려움 )
  • 트랜잭션 바뀌면 동일성 보장 X ( 같은 요청이라도 객체가 다르다 )

확장*범위(선택)확장*범위(선택)

트랜잭션과 무관하게 EntityManager의 생명주기 동안 유지된다.

비유: 학교에서 상담선생님은 항상 같은 상담기록부를 들고 다닌다. // 동일성 보장
     └ 여러 번의 상담(트랜잭션)이 지나도 같은 기록부(영속성 컨텍스트)를 계속 사용한다.

단, 상담선생님이 학교를 그만두게 된다면 상담기록부도 없애버린다.
그리고 다른 상담선생님이 오더라도 이전 기록부는 보지 못한다. // 동일성 보장 안 됨

또한 상담 중 학생 정보가 변경되더라도 선생님은 메모만 해두고,
퇴근(플러시)할 때 한 번에 행정실(DB)에 반영한다. // 변경 감지 + flush

장점

  • 상태별 유지 용이 ( 같은 인스턴스를 유지할 수 있음 )
  • 동일성 유지 ( 같은 인스턴스를 사용 하기에 )
  • 단계별 처리 유리 ( 여러 화면에서 동일한 객체 필요할 때 적합 )

단점

  • 생명주기 관리 어려움 ( 언제 닫힐 지 모름 )
  • 메모리 부담 증가 ( 오랜 시간동안 객체를 등록해 놈 )
  • 멀티스레드 충돌 위험 ( 상태가 공유 되므로 동시성 문제 발생 )
  • Spring과 잘 안맞음 ( 스레드 단위 요청에서는 힘듬 )

영속성 상태

전부 설명하면 길어지고 어려워지니 한번에 설명하도록 하겠다.

실무 측면에서 생각해보면 각각의 영속 상태는 다음과 같은 행동을 취한다.

1. 비영속
  └ DB에 저장되지 않았으며, 아직 영속성 컨텍스트에 등록되지 않은 객체다.
  └ 주로 new 키워드로 생성한 직후이며, persist()를 호출하기 전 상태다.
  └ 실무에서는 입력 폼 데이터를 엔티티로 변환할 때 자주 생성된다.

2. 영속
  └ EntityManager 또는 Spring Data JPA에 의해 관리되고 있는 상태다.
  └ 트랜잭션 안에서 조회되거나 persist()된 객체가 여기에 해당된다.
  └ 실무에서는 조회 후 필드를 수정하면 자동으로 DB에 반영되므로, 매우 많이 활용된다.

3. 준영속
  └ 원래는 영속 상태였지만 detach(), clear(), 혹은 트랜잭션 종료로 관리가 끊긴 상태다.
  └ 변경해도 자동 반영되지 않으며, 다시 반영하려면 merge()를 호출해야 한다.
  └ 실무에서는 배치 처리나 화면에서 객체를 들고 있을 때 발생할 수 있다.

4. 삭제
  └ remove() 호출로 삭제 예약된 상태이며, 커밋 시점에 DB에서 실제 삭제된다.
  └ 삭제 대상은 반드시 영속 상태여야 하며, 그렇지 않으면 예외가 발생한다.
  └ 실무에서는 게시글 삭제, 회원 탈퇴 등의 상황에서 명시적으로 처리된다.

1. 비영속 (Transient)
Student 철수 = new Student("철수");
 └ 철수가 입학원서만 작성했을 뿐, 아직 학교에 제출하지 않은 상태.
 └ 즉, 학교(영속성 컨텍스트)는 철수를 모름.

2. 영속 (Persistent)
em.persist(철수);
 └ 철수가 입학원서를 제출해 학교에 등록됨.
 └ 이제부터 학교는 철수의 출결과 성적을 관리하게 됨.

3. 준영속 (Detached)
em.detach(철수);
 └ 철수가 휴학을 해서 학교와 연결이 끊긴 상태.
 └ 기록은 남아있지만, 학교는 더 이상 출결을 체크하지 않음.

4. 삭제 (Removed)
em.remove(철수);
 └ 철수가 자퇴 신청을 함.
 └ 학교는 이 정보를 DB에서 완전히 삭제할 예정 (트랜잭션 커밋 시 실제 삭제).

영속 등록은 어떻게 하는거지?

내가 말하는 영속 등록이란,
단순히 객체를 new로 생성한 비영속 상태에서 영속 상태로 전이시키는 걸 말한다.

em.persist 또는 repository.save() 를 사용하면 된다.

EntityManager em;

Member member = new Member("kun");  // 비영속 상태
em.persist(member);                // 영속성 컨텍스트에 등록 (영속 상태)
직접 등록해주는 방식

OR

Member member = new Member("kun");     // 비영속 상태
memberRepository.save(member);        // 내부적으로 persist() 또는 merge() 호출됨
간접 등록해주는 방식

QueryDSL 도 EntityManager 을 넣어주던데?

마찬가지로 JPAQueryFactory 가 entityManager 을 쓴다는 의미로 사용된다.
영속 등록(persist) 하려는 게 아니라 단순히 영속성 컨텍스트를 조회 용도로 사용하기 위한 것이에요.


배운점

  • 처음에 개념을 접할떄는 굉장히 어려웠다.

  • 하지만 EntityManager가 관리하는 메모리 공간(1차 캐시)이라는 시각으로 바라보면
    이해가 한결 쉬워졌다.

    다음에는 영속성 전이에 대해서 배워보도록 하겠다.

profile
배우노라, 실험하노라, 기록하노라

0개의 댓글