Back-End

[Java] JPA 영속성

Minch13r 2025. 5. 23. 14:57

1. 영속성 컨텍스트란? 🏠

영속성 컨텍스트는 엔티티를 영구 저장하는 환경을 의미

눈에 보이지 않는 가상의 데이터베이스 같은 공간으로, 엔티티 객체를 보관하고 관리

EntityManager em = emf.createEntityManager(); // 영속성 컨텍스트 생성
em.persist(entity); // 엔티티를 영속성 컨텍스트에 저장

2. 엔티티의 생명주기 (4가지 상태) 🔄

2-1. 비영속(new/transient) 상태 ⚪

  • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 그냥 자바 객체를 생성한 상태 (new 키워드로 생성)
Member member = new Member(); // 순수한 객체 상태 (비영속)
member.setId("member1");
member.setUsername("회원1");

2-2. 영속(managed) 상태 🟢

  • 영속성 컨텍스트에 관리되는 상태
  • persist() 메서드를 통해 영속성 컨텍스트에 저장된 상태
  • 또는 DB에서 조회한 엔티티도 영속 상태
em.persist(member); // 객체를 영속성 컨텍스트에 저장 (영속)
Member findMember = em.find(Member.class, "member1"); // 조회한 엔티티도 영속 상태

2-3. 준영속(detached) 상태 🟡

  • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 영속성 컨텍스트가 제공하는 기능을 사용할 수 없음
em.detach(member); // 특정 엔티티만 준영속 상태로 전환
em.clear(); // 영속성 컨텍스트를 완전히 초기화
em.close(); // 영속성 컨텍스트를 종료

2-4. 삭제(removed) 상태 🔴

  • 삭제된 상태로, 실제 DB 삭제를 요청한 상태
em.remove(member); // 객체를 삭제한 상태 (삭제)

3. 영속성 컨텍스트의 특징과 이점 ✨

3-1. 1차 캐시 📦

  • 영속 상태의 엔티티는 모두 이곳에 저장됨
  • Map<@Id, 엔티티> 형태로 저장
  • 조회 시 DB보다 먼저 1차 캐시에서 찾음 (성능 향상)
// 1차 캐시에 저장됨
em.persist(member);

// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
// 1차 캐시에 없으면 DB에서 조회 후 1차 캐시에 저장하고 반환
Member findMember2 = em.find(Member.class, "member2");

3-2. 동일성(identity) 보장 🔄

  • 같은 트랜잭션 안에서는 같은 엔티티를 반환 (== 비교 true)
  • 1차 캐시 덕분에 가능한 기능
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // true (동일성 보장)

3-3. 트랜잭션을 지원하는 쓰기 지연 ⏱️

  • 트랜잭션 커밋할 때까지 SQL을 모았다가 한번에 전송
  • 버퍼링 기능으로 성능 최적화 가능
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작

em.persist(memberA); // INSERT SQL 생성 → 쓰기 지연 SQL 저장소에 저장
em.persist(memberB); // INSERT SQL 생성 → 쓰기 지연 SQL 저장소에 저장
// 여기까지 SQL을 DB에 보내지 않음

tx.commit(); // 커밋하는 순간 DB에 SQL 모아서 보냄

3-4. 변경 감지(Dirty Checking) 🔍

  • 엔티티의 변경사항을 자동으로 감지
  • 트랜잭션 커밋 시점에 스냅샷과 비교해서 변경된 엔티티 찾음
  • 변경된 엔티티가 있으면 UPDATE SQL 생성 및 실행
Member member = em.find(Member.class, "member1");
member.setUsername("변경된 이름"); // 엔티티 수정
// em.update(member) 같은 코드가 필요 없음!

tx.commit(); // 변경 감지 → UPDATE SQL 실행

3-5. 지연 로딩(Lazy Loading) 🐢

  • 연관된 엔티티를 실제 사용하는 시점에 로딩
  • 성능 최적화에 유용
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 프록시 객체
String teamName = team.getName(); // 실제 team을 사용하는 시점에 DB에서 조회

4. 준영속 상태의 특징 ⚠️

4-1. 준영속 상태로 만드는 방법

  • detach(entity): 특정 엔티티만 준영속 상태로 전환
  • clear(): 영속성 컨텍스트를 완전히 초기화
  • close(): 영속성 컨텍스트를 종료

4-2. 준영속 상태의 특징

  • 영속성 컨텍스트가 제공하는 기능을 사용할 수 없음
  • 식별자 값은 가지고 있음
  • 지연 로딩 불가능
  • 변경 감지 기능 동작 안 함
Member member = em.find(Member.class, "member1"); // 영속 상태
em.detach(member); // 준영속 상태로 전환
member.setUsername("변경"); // 변경해도 DB에 반영되지 않음

5. 병합(merge) - 준영속 → 영속 🔄

5-1. 병합이란?

  • 준영속 상태의 엔티티를 다시 영속 상태로 변경하는 기능
  • 준영속 엔티티의 식별자 값으로 영속 엔티티 조회
  • 없으면 새로 생성해서 병합 (save or update 기능)
// 준영속 상태의 엔티티
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 준영속 엔티티를 영속 상태로 변경
Member mergedMember = em.merge(member);

5-2. 병합 동작 방식

  1. merge() 실행
  2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시 조회
  3. 없으면 DB에서 조회하고, 그래도 없으면 새로운 엔티티 생성
  4. 준영속 엔티티의 값을 영속 엔티티에 모두 복사
  5. 영속 상태의 엔티티를 반환

'Back-End' 카테고리의 다른 글

동기와 비동기  (1) 2025.05.30
[Redis] 기본 개념  (1) 2025.05.26
[DDD] 도메인 주도 설계 애그리거트  (0) 2025.05.22
[Spring] Mybatis  (1) 2025.05.08
[Spring] 어노테이션 세부사항 정리  (1) 2025.04.24