나를 기록하다
article thumbnail
반응형

영속성 컨텍스트란 무엇인가?

JPA를 이해하는데 가장 중요한 용어이다.

  • 뜻: 엔티티를 영구 저장하는 환경
  • EntityManager.persist(entity);
    → 영속성 컨텍스트를 이용해서 DB에 저장하는 것이 아니라 영속성 컨텍스트라는 곳에 저장하는 것
  • 영속성 컨텍스트는 논리적인 개념으로, 눈에 보이지 않는다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.

 

엔티티의 생명주기

  1. 비영속(new/transient): 객체를 생성한 상태(영속성 컨텍스트와 관계 x)
  2. 영속(managed): 객체를 저장한 상태(영속성 컨텍스트에 관리되는 상태)
  3. 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리도니 상태
  4. 삭제(removed): 삭제된 상태

 

[예시] 1차 캐시에서 조회

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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");

            // 영속
            System.out.println("==== BEFORE ====");
            em.persist(member);
            System.out.println("==== AFTER ====");

            Member findMember = em.find(Member.class, 101L);

            System.out.println("findMember.id = " + findMember.getId());
            System.out.println("findMember.name = " + findMember.getName());
            // transaction을 commit하는 순간에 DB에 저장
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
==== BEFORE ====
==== AFTER ====
findMember.id = 101
findMember.name = HelloJPA
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)

→ select 쿼리(조회 쿼리)가 나오지 않았다. 1차 캐시에서 데이터를 바로 가져왔기 때문에 DB에서 select 쿼리를 사용할 필요가 없다.

 

[예시] 1차 캐시, 조회 쿼리를 2번 연속으로 사용했을 때

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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);
            Member findMember2 = em.find(Member.class, 101L);

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

→ 처음에는 1차 캐시에 값이 저장되어 있지 않기에 select문이 나감. 첫번째 조회문을 통해 1차 캐시에 값을 저장했기에 두번째 조회문은 select문이 나가지 않음.

 

 

 

JPA의 특징

1. 영속 엔티티의 동일성 보장

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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);
            Member findMember2 = em.find(Member.class, 101L);

            System.out.println("result = " + (findMember1 == findMember2));

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

→ == 비교(영속 엔티티의 동일성 보장), 자바 컬렉션에서 꺼낸 것과 같음. 이게 가능한 이유는 1차 캐시가 존재하기 때문.

 

2. 엔티티 등록, 트랜잭션을 지원하는 쓰기 지연

쓰기 지연의 개념 설명 - persist를 할 때가 아닌 commit을 하는 시점에 insert 구문 나감.

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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();
    }
}

 

package hellojpa;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
public class Member {

    @Id
    private Long id;
    private String name;

    public Member() {} // 기본 생성자를 작성하지 않으면 JPA에서는 오류 발생(빨간줄)

    public Member(Long id, String name) {
        this.id   = id;
        this.name = name;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
==========================
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)

→ em.persist()를 할 때가 아닌 tx.commit()을 하는 시점에 insert 구문이 나간다.

 

3. hibernate의 batch_size 기능 - 버퍼링

<property name="hibernate.jdbc.batch_size" value="10"/>

사이즈만큼 모아서 데이터베이스에 한번에 네트워크로 쿼리들을 보내고 DB를 마무리한다. (버퍼링 기능, 모아서 한번에)

 

4. 변경 감지(Dirty Checking)

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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); 사용하면 안됨.ㅌ
            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=?

ID 150의 NAME이 A에서 ZZZZZ로 변경

→ 값만 바꿨는데 update 쿼리가 나감. JPA에는 변경 감지 존재

 

5. 플러시(flush)란?

  1. 변경 감지
  2. 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)

영속성 컨텍스트를 플러시 하는 방법

1. em.flush() - 직접 호출

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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(200L, "member200");
            em.persist(member);

            em.flush();

            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
            (?, ?)
==========================

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

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

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

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

→ flush가 안되어있으면 JPQL이 아무것도 조회되지 않음 → 이런 것을 방지하고자 기본 모드가 JPQL을 실행할 때 플러시 자동 호출

 

* 플러시 모드 옵션

  1. FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본값)
  2. FlushModeType.COMMIT: 커밋할 때만 플러시

* 플러시의 특징

1. 영속성 컨텍스트를 비우지 않는다.

2. 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화

3. 트랜잭션이라는 작업 단위가 중요하다 → 커밋 직전에만 동기화하면 된다.

 

 

6. 준영속 상태

특징

  1. 영속 → 준영속
  2. 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
  3. 영속성 컨텍스트가 제공하는 기능 사용 불가

 

방법

1. em.detach(entity) - 특정 엔티티만 준영속 상태로 전환

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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("AAAAA");

            em.detach(member);

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

→ detach를 하였기 때문에 데이터를 변경하였지만 update 쿼리가 나가지 않는다.

 

2. em.clear() - 영속성 컨텍스트를 완전히 초기화

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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("AAAAA");

            em.clear();

            Member member2 = em.find(Member.class, 150L);

            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: 
    select
        member0_.id as id1_0_0_,
        member0_.name as name2_0_0_ 
    from
        Member member0_ 
    where
        member0_.id=?
==========================

→ 영속성 컨텍스트를 완전히 초기화하기 때문에 조회쿼리를 다시 작성해도 똑같은 조회구문이 나간다. testcase 같이 눈으로 보고 싶을 때 사용

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...