카테고리 없음

[JPA] Persistence Context(영속성 컨텍스트)

📝 작성 : 2020.08.17  ⏱ 수정 : 
728x90

Persistence Context

EntityManager로 Entity를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 Entity를 보관하고 관리합니다.
영속성 컨텍스트는 논리적인 개념에 가까우며 EntityManager를 생성 할 때 하나 만들어집니다. 그래서 J2SE환경에서는 EntityManager와 Persistence Context가 1:1로 매칭이 되지만 J2EE환경에서는 N:1로 매칭이 가능합니다.(스프링에서 EntityManager를 주입받아서 사용하면, 같은 트랜잭션 범위에 있는 EntityManager는 동일한 Persistence Context에 접근합니다.)

영속성 컨텍스트의 특징

  1. Entity를 식별자 값(@Id로 테이블 기본키와 매핑한 값)으로 구분합니다. 따라서 반드시 식별자 값이 있어야 하며 없으면 예외가 발생합니다.
  2. 영속성 컨텍스트에 있는 Entity는 보통 트랜잭션을 커밋하는 순간 DB에 반영합니다. 이를 flush라고 합니다.

영속성 컨텍스트가 Entity를 관리하면 생기는 장점

  1. 1차캐시
  2. 동일성(Identity) 보장
  3. 트랜잭션을 지원하는 쓰기 지연
  4. 변경감지
  5. 지연로딩
1차 캐시

영속성 컨텍스트는 내부에 캐시를 갖고 있는데 이를 1차 캐시라고 합니다. 영속 상태의 Entity는 모두 이곳에 저장됩니다.
쉽게 영속성 컨텍스트 내부에 Map<@Id로 매핑한 식별자, Entity 인스턴스> 라고 생각하면 됩니다.

Member member = new Member();
member.setId("m1");
member.setName("name1");
em.persist(member);

라고 했을 경우 Map<m1, member> 라는 map을 내부에 갖고 있다라고 생각하면 됩니다.
Member m = em.find(Member.class, "m1"); 하면 먼저 1차 캐시에서 m1 키를 갖는 엔티티를 찾습니다. 만약 1차 캐시에 없다면 Entity Manager는 DB를 조회하여 해당 Entity를 생성, 1차 캐시에 저장한 후 영속상태의 Entity를 반환합니다.

동일성(Identity) 보장
  • 동일성(Identity) : 실제 인스턴스가 같습니다. => == 비교가 true
  • 동등성(Equality) : 실제 인스턴스는 다를 수 있지만 인스턴스가 갖고 있는 값이 같습니다 => equals() 메서드가 true
Member member1 = em.find(Member.class, "m1");
Member member2 = em.find(Member.class, "m1");

// a == b   => true

쓰기 지연 (transactional write-behind)

Entity Manager는 트랜잭션을 커밋하기 직전까지 내부의 쿼리 저장소에 SQL을 모아둔 후 커밋할 때 한번에 DB에 전송합니다.
예를들어

member.setName("이동현");
member.setName("동현이");
member.setName("dhlee");
transaction.commit();

이렇게 세번 바꾸는 경우 바꿀 때 마다 DB에 전송을 하던 커밋 전에 한번에 3개의 쿼리를 전송하던 결국 같습니다.
이 덕분에 JPA는 쓰기 지연을 지원할 수 있습니다.

변경 감지

Entity를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해 두는데 이를 스냅샷이라고 합니다. flush 시점에 스냅샷과 현재의 엔티티를 비교하여 변경된 부분을 찾습니다. 변경된 부분이 있으면 UPDATE 쿼리를 쓰기지연저장소에 보냅니다.
이때 생성되는 UPDATE SQL은 변경된 필드뿐 아니라 모든 필드를 업데이트 합니다. 이는 DB에 보내는 데이터 전송량이 증가하는 단점이 있지만 아래와 같은 장점이 있습니다.


ㅇ 항상 같은 수정쿼리를 사용함으로써 애플리케이션 로딩 시점에서 수정 쿼리를 미리 생성해두고 재사용 할 수 있습니다.
ㅇ DB는 이전에 한 번 파싱된 쿼리를 재사용 할 수 있습니다.


하지만 필드가 너무 많거나 저장되는 내용이 너무 큰 경우에는 @org.hibernate.annotation.DynamicUpdate 어노테이션을 사용하여 동적으로 업데이트 쿼리를 생성할 수 있습니다.


지연로딩
public class Member {
    ...

    private Team team;
}

pubic class Team {
    ...
}

Member member = em.find(Member.class, "m1"); 
// member 정보만 로딩하고 member에 매핑된 team정보는 로딩하지 않고 프록시 객체에 넣어둡니다. 
Team team = member.getTeam();
// 실제 사용될 때까지 데이터 로딩을 미룹니다.
반응형