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

DaeHoon·2022년 3월 17일
0

JPA

목록 보기
2/5

영속성 컨텍스트 (Persistence Context)

  • 웹 어플리케이션이 구동하는 시점에 EntityManagerFactory를 생성하고 사용자의 요청이 있을 때 EntitiyManager를 생성해 커넥션 풀을 사용해 db를 핸들링 한다.

    • Connection Pool) 웹 컨테이너(WAS)가 실행되면서 일정량의 Connection 객체를 만들어 pool에 저장했다가, 사용자의 요청이 오면 pool에 있는Connection 객체를 사용자에게 넘겨주고 사용이 끝나면 시 Connection 객체를 반납 받아서 pool에 저장하는 기법이다.

영속성 컨텍스트란?

  • 엔티티를 영구 저장하는 환경.
  • 영속석 컨텍스트는 눈에 보이지 않는 논리적인 개념.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근.

엔티티의 생명주기

  • 비영속 (new, transient) : 영속성 컨텍스트와 관계가 없는 상태
  • 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 영속 (managed) : 영속성 컨텍스트에 저장된 상태
  • 삭제 (removed) : 삭제된 상태

Code

1. 비영속

// 비영속 (new, transient)
Member member = new Member();
member.setName("dh");
member.setId(1L);

2. 영속

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
// member 객체를 저장한 영속 상태 (managed)
em.persist(member);

3. 준영속, 삭제

// 회원 엔티티를 영속성 컨텍스트에서 분리한 준영속 상태 
em.detach(member);
// 삭제 (remove)
em.remove(member);

영속성 컨텍스트의 장점

1. 1차 캐시 역할

Member member = new Member();
member.setName("대훈");
member.setId(1L);

em.persist(member);
Member findMember = em.find(Member.class, "1L");
  • 처음에 영속 컨텍스트안에 엔티티가 있는지 확인.
  • 엔티티가 없을 시 데이터베이스에서 조회해온다.
  • 1차 캐시는 트랜잭션 내부에서 만들어지고 종료되기 때문에 트랜잭션이 종료될 경우 캐시는 다 사라진다.

2. 동일성(identity) 보장

Member member = new Member();
Member findMember = em.find(Member.class, "1L");
Member findMember2 = em.find(Member.class, "1L");

system.out.println(findMember == findmember2); 
  • true를 출력한다.

3. 쓰기 지연

Member member = new Member(2L, "dh");
Member member2 = new Member(3L, "dh");
 
em.persist(member);
em.persist(member2); //  쓰기 지연 SQL 저장소에 저장
System.out.println("Lazy Loading");
tx.commit();
14:32:19.411 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
Lazy Loading
14:32:19.412 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
  /* insert com.example.jpalearning.Member
        */ insert 
        into
            Member
            (USERNAME, MEMBER_ID) 
        values
            (?, ?)
Hibernate: 
    /* insert com.example.jpalearning.Member
        */ insert 
        into
            Member
            (USERNAME, MEMBER_ID) 
        values
            (?, ?)
14:36:07.150 [main] DEBUG org.hibernate.SQL - 
    /* insert com.example.jpalearning.Member
        */ insert 
        into
            Member
            (USERNAME, MEMBER_ID) 
        values
            (?, ?)
Hibernate: 
    /* insert com.example.jpalearning.Member
        */ insert 
        into
            Member
            (USERNAME, MEMBER_ID) 
        values
            (?, ?)org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:32:19.414 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:32:19.414 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
  • 로그를 보면 commit 메소드가 실행되는 이후에 쿼리 로그가 발생한다.

  • 출력문 이후 쓰기 SQL 저장소에 저장된 쿼리들을 다 실행시켜 Database에 저장한다.

4. 엔티티 수정 - 변경 감지 (Dirty Checking)

tx.begin();
            
Member findMember = em.find(Member.class, 2L);
findMember.setId(24L);
            
tx.commit();

  • 데이터를 가져와 값을 변경할 때, persist로 쓰기 지연 SQL 저장소에 알리지 않아도 트랜잭션이 커밋이 될 때 알아서 db에 저장된다. 이게 가능한 이유는 JPA의 변경 감지라는 개념 덕분이다.
  • 1차 캐시 안에는 PK, Entity, 스냅샷이 존재. 여기서 스냅샷은 최초로 영속성 컨텍스트 (1차 캐시)에 들어오는 순간 스냅샷을 찍어 저장한다.
  • JPA는 트랜잭션이 커밋되는 순간에 엔티티와 스냅샷을 비교한다. 이 때 변경이 될 경우 쓰기 지연 SQL 저장소에 업데이트 된 쿼리를 저장하고 수행하게 된다.

5. 엔티티 삭제

Member member = em.find(Member.class, 1L);
em.remove(member) // 삭제

플러시 (Flush)

  • 영속성 컨텍스트의 변경 내용을 DB에 반영하는 작업.
  • 트랜잭션에서 커밋이 일어날 때 flush가 동작하는데, 이 때 쓰기 지연 SQL 저장소에 저장된 SQL들이 DB에 날아간다. 이 떄 영속성 컨텍스트의 내용은 비워지지 않고 저장된 상태로 존재한다.
  • 즉 영속성 컨텍스트의 변경 사항과 DB의 상태를 맞추는 일종의 Sync 작업

동작 과정

  1. 변경을 감지한다 (Dirty Checking)
  2. 수정된 Entity를 쓰기 지연 SQL 저장소에 등록한다.
  3. 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송한다. (CUD Query)

플러시 하는 방법

1. flush 메소드를 통한 직접 호출

tx.begin();

Member member = new Member(200L, "A");
em.persist(member);

em.flush(); // 강제 호출 

System.out.println("Commit 전에 Query가 DB로 날아감");

tx.commit();
  • 메소드 명만 보면 데이터가 내려갈 것 같지만, flush가 일어났다고 1차 캐시가 지워지지 않는다.
15:09:31.976 [main] DEBUG org.hibernate.SQL - 
    /* insert com.example.jpalearning.Member
        */ insert 
        into
            Member
            (USERNAME, MEMBER_ID) 
        values
            (?, ?)
Hibernate: 
    /* insert com.example.jpalearning.Member
        */ insert 
        into
            Member
            (USERNAME, MEMBER_ID) 
        values
            (?, ?)
DB INSERT Query 가 즉시 나감. -- flush() 호출 후 --  Transaction commit 됨.
15:09:31.981 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
15:09:31.981 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
15:09:31.981 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
.
.
.
  • 쿼리가 먼저 날아간 뒤에 커밋이 되는 것을 볼 수 있다.

2. 트랜잭션 커밋 시 플러시 자동 호출.

3. JPQL 쿼리 실행 시 플러시 자동 호출

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

// 중간에 JPQL 실행
query = entityManager.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();

Q. 영속성 컨텍스트에 저장한 뒤 find 함수를 통해 조회를 시도하면 조회가 될까?
A. 조회가 되지 않음. flush가 실행되지 않은 상태라 Insert 문이 쓰기 지연 SQL 저장소에만 있는 상태일 것이다.

그래서 JPA의 디폴트로 JPQL 쿼리 실행 시 flush를 자동으로 날리게 되고 Insert가 된 뒤에 select절이 실행이 된다.


강의 : 자바 ORM 표준 JPA 프로그래밍

profile
평범한 백엔드 개발자

0개의 댓글