기록과 정리

영속성 컨텍스트 ( Entity Manager ) 본문

IT/JPA

영속성 컨텍스트 ( Entity Manager )

zepetto 2021. 2. 9. 20:00

이 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 참고하였습니다.

www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그

www.inflearn.com

영속성 컨텍스트란?

 

영속성 컨텍스트란 ' 엔티티를 영구 저장하는 환경' 이라는 뜻입니다.  논리적인 개념이라 눈에 보이지 않습니다. Entity Manager (이하 em) 를 통해 영속성 컨텍스트에 접근합니다. 

 

J2SE 환경에서는 em과 영속성 컨텍스트 ( 흔히 이해하는 1차 캐시의 영역 ) 는 1:1 환경이지만 J2EE , 스프링에서는 1:N관계를 가집니다. 

 

1차 캐시 / 쓰기 지연 SQL 

영속성 컨텍스트에는 2가지 영역이 존재합니다. 1차캐시와 쓰기 지연 SQL 영역입니다. 

영속성 컨텍스트

영속 상태가 되면 ID값과 Entity는 구분되어 임시 저장이 됩니다. ( *추가적으로 DB와의 sync를 맞춰주는 플러시 영역이 존재합니다. 원래 entity와 비교하여 update시 DB와 동기화를 해주는 영역이 존재합니다. ) 

영속 상태가 된 객체의 해당 쿼리는 쓰기지연SQL이라는 영역에 잠시 저장이 되었다가 Transaction이 Commit이 되는 순간 DB로 요청을 보내 return 값을 받게 됩니다.

엔티티 생명주기

엔티티에는 크게 4가지의 생명 주기가 존재합니다.

 

1. 비영속 ( new / transient ) 

영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

Member member = new Member();
member.setId("member1")
member.setUsername("zepetto")

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

-> 객체만 생성한 상태입니다.

 

2. 영속 (managed)

영속성 컨텍스트에 관리되는 상태

 

Member member = new Member();
member.setId("member1")
member.setUsername("zepetto")

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

em.persist(member1)

-> persiste를 통해 영속성 컨텍스트에 관리되기 시작합니다.

해당 member1이라는 객체는 영속성 컨텍스트에 1차 캐시를 통해 id는 'member1' 이고 해당 객체는 'member'로 관리 되어집니다.

또한 쓰기 지연 SQL 이라는 영역에 insert쿼리를 저장합니다. 해당 쿼리는 트랜잭션 커밋이 되는 순간 DB로 전달이 됩니다. 그 전까지는 DB로 전달이 되지 않습니다. 

 

따라서 저 코드는 DB에 insert 되지 않습니다.

 

3. 준영속 (detached)

영속성 컨텍스트에 저장되었다가 분리된 상태

Member member = em.find(Member.class, 150L);
member.setName("AAAA");

em.detach(member);

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

결과가 어떻게 될까요? 순서대로 집어보겠습니다.

 

결과

member 라는 객체 생성 후 find를 합니다. member.setName("AAA") 을 통해 '더티 체킹'을 합니다.

보통 update를 해줄때 저렇게 해주고 무언가 save라던가 update를 해주어야할 것 같지만

set을 해준 순간 update쿼리는 쓰기지연SQL 영역에 전달이 됩니다.

commit시점에 update쿼리가 DB에 전달이 되죠. 

 

자바 컬렉션처럼 사용하는 것이 JPA의 전략이기 때문입니다. 하지만 detach를 통해 준영속 (분리) 상태가 되었기 때문에 commit 시점이 되어도 update쿼리는 적용이 안됨을 볼 수 있습니다. ( 1차 캐시 영역에는 데이터가 남아있습니다. 단지 관리를 하지 않는 상태이며 물리적인 데이터는 남겨져 있습니다.)

실제로 캐시에 남아있는지 확인하는 코드, 다시 영속관리를 할시 pk 중복으로 인해 에러가 난다.

준영속 상태로 만드는 방법은 또한 em.clear()를 통해 해당 Entity Manager를 통째로 날리는 방식도 있습니다. 그외에도 em.close() 존재.

 

4. 삭제 (removed)

물리적으로 캐시 삭제, 그리고 delete쿼리를 쓰기 지연 영역에 전달합니다. commit 시점에는 쿼리가 DB로 전달되면서 데이터는 삭제 됩니다.

 

 

플러시

위에서 간단한 설명이 있지만 플러시는 영속성 컨텍스트의 변경 내용을 데이터 베이스에 반영합니다. 즉 영속성 컨텍스트와 DB와의 변경사항을 Syncronize해줍니다. 

 

변경감지 -> 수정된 엔티티 쓰기 지연 SQL 저장소에 등록 -> 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송

 

이와 같은 순서로 DB로 전달이 됩니다. 

 

플러시를 하는 방법은 3가지입니다.

1. em.flush() 직접 호출

 

public class jpaMain {
    public static void main(String[] args) {

        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        final EntityManager em = emf.createEntityManager();
        final EntityTransaction tx = em.getTransaction();

        tx.begin();

        try{
            Member member1 = new Member(200L , "member200");
            em.persist(member1);

            em.flush();
            System.out.println("=============================");
            tx.commit();
        }catch(Exception e){
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();
    }
}

해당 코드를 통해 직접 호출을 해보면

======== 을 찍은 부분이 보이지 않습니다. commit이 되기도 전에 강제로 flush 되어 db로 쿼리가 날아간것을 확인할 수 있습니다.

 

두번째로 트랜잭션 커밋시 ,세번째 JPQL 쿼리 호출시 자동 호출이 됩니다. 두세번째가 일반적인 방식이라고 볼 수 있겠습니다.

 

이상 영속성 컨텍스트에 대한 정리였습니다. 부족하지만 읽어주셔서 감사합니다.