JPA - Persist Context (영속성 컨텍스트)

목차

Persist Context (영속성 컨텍스트) 란?

JPA 가 Entity 를 관리하기 위한 공간, Entity 상태가 Database 에 반영되기 전까지 Entity 의 읽기/쓰기/수정/삭제를 상태 관리한다.

  • EntityManagerFactory 에서 EntityManager 를 생성하고 EntityManager 를 이용해 영속성 컨텍스트에 접근할 수 있다.
  • EntityManager 를 이용해 Entity(데이터)를 저장하거나 조회하면 영속성 컨텍스트에서 해당 Entity 를 관리하게 된다.
  • EntityManager 는 Transaction 단위로 영속성 컨텍스트를 생성하고 지운다.
  • EntityManager 와 영속성 Context가 1:1로 대응이 된다.

영속성 Context는 내부에 1차 cache가 존재하고 (key, value) 형태로 데이터를 관리한다.
장점 : buffering이나 Caching을 할 수 있다.

@Id Entity
Member1 member
  • @Id : Key
  • Entity : 객체

영속성 컨텍스트의 이점

  • 1차 캐시
    • Database 에서 읽어온 데이터를 임시적으로 저장하고 관리한다.
  • 동일성 보장
    • 같은 Key 를 통해 조회할때 반환되는 객체는 항상 같다.
  • 트랜잭션을 지원하는 쓰기 지연
    • Entity 에 대한 변경 작업이 끝나면 Database 에 반영한다.
  • 변경 감지
    • Entity 가 변경이 일어나면 Database 에 반영한다. 변경이 없을 경우에는 반영하지 않는다.
  • 지연 로딩
    • 실제 객체를 호출 전까지는 Proxy 객체를 Loading 해 놓는다.

1차 캐시에서 조회

조회를 했지만 select 쿼리가 나가지 않는다. persist로 저장 시에는 1차 캐시에 데이터가 저장되기 때문이다. 조회를 할 때 DB를 조회 한 것이 아닌 우선적으로 1차 캐시를 조회해 값을 가져오기 때문에 DB에 select 쿼리가 나가지 않았던 것이다.

public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

try {
// 비영속 상태
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");

// 영속 상태
em.persist(member);

// Member Entity 를 조회한다.
Member findMember = em.find(Member.class, 101L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());

tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}

Member Entity 를 조회 했지만 따로 Select 쿼리문이 나가지 않고, 새로운 데이터를 넣기 위한 Insert 문이 발생힌다.

findMember.id = 101 findMember.name = HelloJPA 
Hibernate: /* insert hellojpa.Member */
insert into Member (name, id) values (?, ?)

1차 캐시에 data가 없는 경우

  1. DB에서 조회
  2. 1차 Cache에 데이터를 저장(있으면)했으면 해당 객체를 가져와서 사용
  3. 1차 Cache에 없을 경우 Database 에 접근해 데이터를 가져온 후 1차 Cache 에 저장한다.

똑같은 Key(조회조건) 를 가지고 조회 했을 때 쿼리가 한번만 나가고 그 다음에는 1차 캐시(영속성 컨텍스트) 에 저장된 Entity 객체를 가져온다.

public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

try {
Member findMember1 = em.find(Member.class, 101L); // 첫번째 조회시에는 query가 나간다.
Member findMember2 = em.find(Member.class, 101L); // 두번째 조회시에는 나가지 않는다.

tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}

두번의 조회를 했지만 하나의 select 문만 발생하는 것을 확인할 수 있다.

Hibernate: 
select member0_.id as id1_0_0_, member0_.name as name2_0_0_
from Member member0_
where member0_.id=?

영속 엔티티의 동일성 보장

Member findMember1 = em.find(Member.class, 101L); // 첫번째 조회시에는 query가 나간다.
Member findMember2 = em.find(Member.class, 101L); // 두번째 조회시에는 나가지 않는다.

System.out.println("result = " + (findMember1 == findMember2)); // true가 나온다.
result = true

트랜잭션을 지원하는 쓰기 지연

commit()을 실행했을 때 query문이 날라가는 것을 확인할 수 있다.

public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

try {
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");

em.persist(member1);
em.persist(member2);

System.out.println("==========================");

tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
Hibernate: 
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)

엔티티 수정

public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

try {
Member member = em.find(Member.class, 150L);
member.setName("ZZZZZ");
// em.persist(member); persist를 사용할 필요가 없다.

System.out.println("==========================");

tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
Hibernate: 
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
==========================
Hibernate:
/* update
hellojpa.Member */ update
Member
set
name=?
where
id=?
Share