본문 바로가기

Dev/JPA

RS) 영속성 컨텍스트

영속성 컨텍스트란?

  • JPA를 이해하기 위한 가장 중요한 용어
  • EntityManager.persist(entity);
  • persist 하는 순간 엔티티를 영속성 컨텍스트에 만든다. (영속화 한다)
  • 엔티티 매니저를 통해서 접근한다.
  • 눈에 보이지 않는 공간이 생긴다.

엔티티의 생명주기

비영속

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

비영속 상태

// 객체를 생성한 상태로 (비영속 상태)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

영속

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

- 영속상태가 됬다고 바로 DB에 쿼리가 날라가는 것은 아니다.

- 트랜잭션 커밋하는 순간 DB에 쿼리가 날라간다.

영속 상태

// 객체를 생성한 상태로 (비영속 상태)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

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

// 객체를 저장한 상태(영속성 상태)
em.persist(member); // persist 하는 순간 영속성 컨텍스트에 member라는 엔티티가 올라간다.

 

준영속

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

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

삭제

- 삭제된 상태

em.remove(member);

영속성 컨텍스트의 이점

  • 영속성 컨텍스트는 내부에 1차캐시라는 공간을 들고 있다.
  • 영속성 컨텍스트를 1차캐시라고 이해해도 된다.

 

영속성 컨텍스트의 동작 정리

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// ==== 여기까지는 준영속 상태(객체 생성) ====

// 1차 캐시에 저장
em.persist(member);
/*
==== persist 하는 순간 영속성 상태가 되고, 
	 영속성 컨텍스트가 만들어지며 1차캐시에 member객체가 저장됨 ====
*/

// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
/*
	==== em.find 하는 순간 JPA는 1차캐시에서 먼저 member1의 키 값을 가지고 찾는다 
		1차캐시에 member1 키 값과 데이터가 저장되어있으므로 DB에 들리지않고 바로 반환해준다 ====
*/

// 데이터베이스에서 조회
Member findMember2 = em.find(Member.class, "member2");
/*
	==== em.find를 하여 member2 키 값을 가지고 1차캐시를 찾았지만 없을 경우
    	 데이터베이스에 들려서 찾고 1차캐시에 새로운 member2 키 값과 데이터를 넣어주고 반환한다 ====
*/

Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L); // 여기선 select문이 나가면 안됨

/*
	- 실제 select 문이 한번만 나간다.
    Hibernate: 
        select
            member0_.id as id1_0_0_,
            member0_.name as name2_0_0_ 
        from
            Member member0_ 
        where
            member0_.id=?
*/

 

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

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // 트랜잭션 시작

em.persist(memberA);
em.persist(memberB);
// persist를 하더라도 실제 DB에 SQL문을 보내지 않는다.
// === 영속성 컨텍스트 안에 쓰기지연 SQL 저장소에 쌓아둔다. ===

// 트랜잭션을 커밋하는 순간 데이터베이스에 쓰기지연 SQL 저장소에 쌓아두었던 SQL문을 보낸다.
transaction.commit(); // 트랜잭션 커밋

영속성 컨텍스트 (1차캐시 + 쓰기지연SQL 저장소) 의 구조

위의 그림 내부에서 동작 과정

  1. memberA를 저장하는 순간 1차 캐시에 저장함과 동시에 쓰기지연 SQL 저장소에 INSERT문(SQL)을 저장해둔다.
  2. memberB를 저장하는 순간 1차 캐시에 memberB도 저장해두고 쓰기지연 저장소에 마찬가지로 쌓아둔다.
  3. 데이터베이스에 날라가는 순간은 트랜잭션을 커밋하는 순간이다.
  4. 트랜잭션을 커밋하게 되면 쓰기지연 SQL 저장소에 쌓아둔 쿼리들이 flush 되면서 데이터베이스에 날라가게 된다.
 try{
            Member member1 = new Member(150L, "A");
            Member member2 = new Member(160L, "B");

            em.persist(member1);
            em.persist(member2);
            System.out.println("=================");

            tx.commit(); // 커밋하는 순간 디비에 날라간다.
            
 /* 실행 결과
 ================= ---> 선을 먼저 긋고나서 쿼리가 나간다. */
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)

 

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

변경 감지(Dirty Checking)

  • JPA는 트랜잭션이 커밋하는 시점에 내부적으로 flush() 가 호출된다.
  • 1차 캐시 안에서 엔티티와 스냅샷을 비교한다.
    • 1차 캐시는 엔티티와 스냅샷으로 이루어져 있다.
    • 스냅샷은 1차캐시에 최초로 들어온 상태를 본떠놓는 것
    • 값이 변경될 경우 변경을 감지해서 트랜잭션이 커밋하는 순간 JPA가 엔티티와 스냅샷을 일일히 비교한다.
    • 바뀐것이 확인되면 UPDATE 쿼리를 쓰기지연 저장소에 저장해두고 커밋하는 순간 DB에 쿼리를 날려준다.
try{
    Member member = em.find(Member.class, 150L); // 데이터를 찾아온다음에
    member.setName("ZZZZZ"); // 변경만 해주면 된다.
    // 별도로 변경한 값을 다시 넣어줄 필요 없다. (persist를 다시 해줄 필요 없다는 뜻)

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

    tx.commit(); // 커밋하는 순간 디비에 날라간다.
    
/* 실행 결과 */

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=?

'Dev > JPA' 카테고리의 다른 글

RS) 준영속 상태  (0) 2022.02.01
RS) 플러시(flush)  (0) 2022.02.01
RS) JPA 어플리케이션 실습  (0) 2022.02.01
RS) JPA(Java Persistence API)  (0) 2022.02.01
RS) SQL 중심적인 개발의 문제점  (0) 2022.02.01