본문 바로가기
JPA

[JPA] 영속성 관리

by snow_white 2022. 7. 18.
※ 인프런 강의 김영한님의 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 

 

JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기
  • 영속성 컨텍스트

영속성 컨텍스트란?

  • 엔티티를 영구 저장하는 환경
  • 어플리케이션과 DB사이에서 객체를 보관하는 가상의 DB같은 역할
  • 서비스별로 하나의 EntityManager Factory가 존재하며 Entity Manager Factory에서 디비에 접근하는 트랜잭션이 생길 때 마다 쓰레드 별로 Entity Manager를 생성하여 영속성 컨텍스트에 접근
  • EntityManager에 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리
  • 영속성 컨텍스트는 EntityManager를 생성할 때 만들어지며 EntityManager를 통해 영속성 컨텍스트에 접근하고 관리

Entity를 영속성 컨텍스트에 저장하기 👉 persist()

entityManager.persist(entity);

Spring에서는 EntityManager를 주입하여 사용하면 같은 트렌잭션의 범위에 있는 EntityManager는 같은 영속성 컨텍스트에 접근한다. (J2EE와 같은 스프링 프레임워크 같은 컨테이너 환경은 Entity Manager와 영속성 컨텍스트의 관계가 N:1이 가능하다. 하지만 J2SE와 같은 환경은 1:1이다.)

 

EntityManager

  • EntityManager는 영속성 컨텍스트 내에서 Entity들을 관리하고 있다.
  • EntityManager는 JPA에서 제공하는 interface로 spring bean으로 등록되어 있어 Autowired로 사용할 수 있다.
@Autowired
private EntityManager entityManager;
  • Query Method, Simple JPA repository는 직접적으로 entityManager를 사용하지 않도록 한번 더 감싸준 것이다.
  • spring jpa에서 제공하지 않는 기능을 사용하거나 특별한 문제가 있어서 별도로 customizing을 해야한다면 entityManager를 직접 받아서 처리한다.
  • EntityManager는 Entity Cache를 갖고 있다.

1. 비영속 상태 (new/transient)

객체만 생성하고 엔티티와 연결하지 않음, 영속 컨텍스트에 들어가지 않음

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

 

2. 영속 상태 (managed) → @Id로 매핑된 키값이 있어야함

객체를 생성하고 저장한 상태,

영속성 컨텍스트가 관리하는 엔티티(1차캐시/쓰기지연저장소에 있는 상태)

//객체를 생성만 한 상태(비영속)
Member member = new Member();
member.setId("member1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

//객체를 저장한 상태(영속)
em.persist(member);

3. 준영속 상태 (detached)

엔티티가 영속성 컨텍스트에서 분리

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

 

4.삭제 상태 (removed)

엔티티 삭제

// 객체를 삭제한 상태(삭제)
em.remove( );

✅영속성 컨텍스트의 이점

1차 캐시에서 조회

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 ===");
            // 1차 캐시에 저장
            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());

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

        emf.close();
    }
}

Console 출력 결과

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

❓❗ 조회를 했는데 DB에서 SELECT 쿼리문을 실행하지 않은 이유

em.persist(member) 에서 데이터를 저장할 때 1차 캐시에 저장이 되었기 때문!

그리고 데이터를 저장했을 때와 같은 PK로 조회를 했기 때문에 DB에서 가져오는 것이 아니라 1차 캐시에 있는 것을 먼저 조회한다.

 

데이터베이스에서 조회

// 데이터베이스에서 조회
// 영속
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);

Console 출력 결과

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

❓❗ SELECT 문이 한 번만 실행된 이유

PK가 101번인 데이터를 가져올 때 JPA는 무조건 영속성 컨텍스트에 올려두기 때문에 findMember2에서 똑같은 PK로 조회할 때 1차 캐시에 올려져 있는 데이터 먼저 탐색한다.

 

영속 엔티티의 동일성 보장

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

같은 트랜잭션 내에서 보장 가능함

👉 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

 

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

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

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

// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
System.out.println("=====================");

// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
tx.commit();

Console 출력 결과

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

👉 persist() 이후에 쿼리문 실행

 

엔티티 수정 - 변경 감지

 

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

👉 데이터 수정 이후에 persist()나 update() 같은 코드가 있어야 하지 않을까?

JPA는 DB 트랜잭션을 commit하는 순간 영속성 컨텍스트 내부의 1차 캐시에서 Entity와 스냅샷을 비교하게 된다.

비교해서 값이 변경되었다면 UPDATE 쿼리를 '쓰기 지연 SQL 저장소'에 저장해 DB에 반영한 다음에 commit이 이루어진다.

 

엔티티 삭제

// 삭제 대상 엔티티 조회
Member member = em.find(Member.class, 150L);
em.remove(member); // 엔티티 삭제

'JPA' 카테고리의 다른 글

[JPA] 연관관계 매핑 - 단방향 연관관계  (0) 2022.08.07
[JPA] 엔티티 매핑  (0) 2022.07.28
[JPA] 준영속 상태  (0) 2022.07.18
[JPA] 플러시(Flush)  (0) 2022.07.18
[JPA] JPA의 소개  (0) 2022.07.17

댓글