본문 바로가기
개발/Spring

영속성 컨텍스트의 이해: JPA의 핵심 기능 탐구

by 난중후니 2024. 1. 12.
728x90
반응형
  1. 영속성이란?
  • 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 말한다.
  • 영속성을 갖지 않으면 데이터는 메모리에서만 존재하게 되고 프로그램이 종료되면 해당 데이터는 모두 사라지게 된다.
  • 그래서 데이터를 파일이나 DB에 영구 저장함으로써 데이터에 영속성을 부여한다.
  1. 영속성 컨텍스트란?
  • 엔티티(Entity)를 영구 저장하는 환경
  • Application과 데이터베이스 사이에서 객체를 보관하는 가상의 저장소 같은 역할
  • 엔티티 매니저(Entity Manager)를 통해 영속성 컨텍스트에 접근
  • EntityManager를 통해 Entity를 저장, 조회하면 EntityManager는 영속성 컨텍스트에 해당 Entity를 보관하고 관리
  1. 영속성 컨텍스트 생명주기
  • 비영속(new): 영속성 컨텍스트와는 무관한 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 영속성 컨텍스트에서 삭제된 상태

3-1. 비영속(new)

  • Entity 객체를 생성했지만, 아직 영속성 컨텍스트에는 저장되지 않은 상태(순수 객체)
// 순수 객체
Member member = new Member();

3-2 영속(Managed)

  • Entity Manager를 통해서 Entity를 영속성 컨텍스트에 저장한 상태
  • 해당 객체는 영속성 컨텍스트에 의해 관리된다는 의미
// 순수 객체
Member member = new Member();

// 영속성 컨텍스트에 저장 -> 영속성 컨텍스트가 관리
EntityManager em;
em.persist(member);

3-3 준영속(Detached)

  • 영속성 컨텍스트가 관리하던 상태에서 엔티티를 더이상 관리하지 않는 상태
  • 준영속 상태의 특징
    • 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
    • 식별자 값을 가지고 있다.
// 엔티티를 영속성 컨텍스트에서 분리
em.detach(member);
// 영속성 컨텍스트를 비움(초기화)
em.clear();
// 영속성 컨텍스트 종료
em.close();
  • 엔티티를 준영속 상태로 전환하는 방법
  • detach(entity)
    • 특정 엔티티만 준영속 상태로 전환(영속성 컨텍스트로부터 분리)
    • 1차 캐시, 쓰기 지연, SQL 저장소 정보 제거
    • 영속성 컨텍스트 안에서의 Insert, Update 쿼리도 제거되어 DB에 저장되지 않는다.
  • clear()
    • 영속성 컨텍스트를 완전히 초기화
    • 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만든다
    • 영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태
  • close()
    • 영속성 컨텍스트를 종료
    • 해당 영속성 컨텍스트가 관리하던 영속성 상태의 엔티티들은 모두 준영속 상태로 변경

엔티티를 영속 상태로 전환하는 방법

  • merge(entity)
    • 준영속 상태의 엔티티를 다시 영속 상태로 변경(병합)
    • 파라미터로 전달된 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 엔티티가 없다면 DB에서 조회
    • 만약 DB에도 없다면 새로운 엔티티를 생성하여 병합
    • 병합은 save(저장) 또는 update(수정) 기능 수행

3-4 삭제(Remove)

  • 엔티티를 영속성 컨텍스트와 DB에서 삭제
em.remove(member);

4. 특징

  • 엔티티 매니저(Entity Manager)를 생성할 때 영속성 컨텍스트(Persistence Context)도 생성 (1:1)
  • 엔티티 매니저를 통해 해당 영속성 컨텍스트에 접근, 관리 할 수 있음
  • 엔티티를 식별자 값(@id로 매핑한 값)으로 구분
  • 영속성 상태에는 식별자 값이 반드시 있어야 한다.(없으면 예외 발생)

트랜잭션을 Commit 하는 순간 영속성 컨텍스트에 새로 저장된 엔티티(Entity)를 DB에 반영

  • 영속성 상태에서 값을 여러 번 바꾸어도 마지막 트랜잭션을 커밋하는 순간 값으로 반영(flush)

영속성 상태의 엔티티는 모두 영속성 컨텍스트에서 관리

  • 내부 캐시(1차 캐시)에 저장
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩
  1. 예시
  • Member Entity
@Entity
@Getter
@Setter
public class Member{
    @Id
    private String id;

    private String name;
}
// 엔티티 생성(비영속 = 순수객체)
Member member = new Member();
member.setId("member1");
member.setName("테스트1");

Member mebmer2 = new Member();
member2.setId("member2");
member2.setName("테스트2");

// 엔티티 영속
EntityManager em;
em.persist(member1); // id = test1
em.persist(member2); // id = test2

1차 캐시

  • 영속성 컨텍스트는 내부에 캐시(Cache)가 존재(1차 캐시)
  • 영속 상태의 엔티티는 해당 캐시에 저장
  • 캐시는 Map 형태로 구성되어 있으며, 키(Key)는 @Id로 매핑한 식별자이며 값(Value)는 엔티티 인스턴스
    • 1차 캐시의 키는 식별자 값
    • 식별자 값은 DB 기본키와 매핑
    • 영속성 컨텍스트에서 데이터를 저장하고 조회하는 모든 기준은 DB 기본 키 값

엔티티 조회

Member member = em.find(Mmeber.class, "member1"); // id = member1
  • 1차 캐시에서 해당 Key에 대한 엔티티가 있는지 조회
  • 엔티티 조회 시 우선 1차 캐시에서 식별자 값으로 엔티티를 찾음
  • 엔티티가 존재하면 DB를 조회 하지 않고, 메모리에 있는 1차 캐시에서 해당 엔티티를 조회

  • 엔티티가 1차 캐시에 없다면, DB에서 조회
    • 엔티티가 1차 캐시에 없으면 엔티티 매니저는 DB를 조회해서 해당 엔티티를 1차 캐시에 저장
    • 1차 캐시에 저장된 후 영속성 상태의 엔티티를 반환

  • 영속성 엔티티의 동일성 보장
Member m1 = em.find(Member.class, "member1");
Member m2 = em.find(Member.class, "member1");

System.out.println(a == b); // treu(동일성 보장)

엔티티 등록

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

// 데이터 변경 시 트랜잭션을 시작
transaction.begin();

em.persist(member1);
em.persist(member2);
// 여기까지는 INSERT 쿼리를 DB에 보내지 않음

// 커밋하는 순간 DB에 INSERT 쿼리를 보냄
transaction.commit();
  • 트랜잭션(Transaction)을 지원하는 쓰기지연
    • 엔티티 매니저는 Transaction(트랜잭션)을 커밋하기 전까지 DB에 엔티티를 저장하지 않음
    • 엔티티 매니저 안에 존재하는 SQL 저장소에 INSERT SQL을 별도로 저장
    • 트랜잭션이 커밋이 될 때 SQL 저장소에 있는 모든 INSERT SQL을 DB에 요청

  • 플러시(flush)
    • 트랜잭션이 커밋될 때 엔티티 매니저는 flush를 실행
    • 트랜잭션 커밋 요청 -> 엔티티 매니저 flush 실행 -> DB에 쿼리 요청(동기화)

엔티티 수정

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

// 데이터 변경 시 트랜잭션을 시작
transaction.begin();

// 엔티티 조회(영속성)
Member m1 = em.find(Member.class, "member1");

// 엔티티 데이터 수정
m1.setName("Developer");

// 커밋하는 순간 DB에 UPDATE 쿼리를 보냄
transaction.commit();
  • JPA에서는 엔티티를 수정할 때는 엔티티를 조회하여 데이터를 변경하여 저장(update() 메소드 없음)
  • 변경 감지 기능을 통해 DB에 자동 반영(DB Update)
  • 변경 감지는 영속성 컨텍스트(Entity Manager)가 관리하는 영속 상태의 엔티티에만 적용

  • 수정 순서
    • 트랜잭션 커밋 요청(엔티티 매니저 내부에서 먼저 플러시 호출)
    • 엔티티 스냅샷과 비교하여 변경된 엔티티를 찾음
    • 변경된 엔티티가 발견되면 Update 쿼리를 생성하여 쓰기 지연 SQL 저장소에 저장
    • 다시 플러시를 호출하면서 쓰기 지연 저장소의 쿼리를 DB에 요청
    • DB 커밋
  • 업데이트의 기본 전략
    • JPA의 기본 전략은 모든 엔티티의 필드를 업데이트하는 것이다.
    • 모든 필드를 사용하면 수정 쿼리가 항상 같다.
    • 동일한 쿼리를 보내면 DB는 이전에 파싱된 쿼리에 대해서는 재사용을 한다.

엔티티 삭제

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

// 데이터 변경시 트랜잭션을 시작
transaction.begin();

// 엔티티 조회(영속성)
Member member1 = em.find(Member.class, "member1");

// 엔티티 데이터 삭제
em.remove(member1);

// 커밋하는 순간 DB에 DELETE 쿼리를 보냄
transaction.commit();
  • 엔티티를 삭제하기 위해서도 먼저 삭제 대상의 엔티티 조회가 필요
  • DB에서 바로 삭제되는 것이 아니라, 영속성 컨텍스트(엔티티 매니저)에서만 제거
  • DELETE 쿼리를 쓰기 지연 SQL 저장소에 저장.
  • 트랜잭션 커밋 시 쿼리를 DB에 요청
  • em.remove(member1)를 호출하는 순간, member1은 영속성 컨텍스트에서 제거되어, 더 이상 수정할 수 없다.
728x90
반응형

댓글