티스토리 뷰

Spring/JPA

JPA - 영속성 컨텍스트

땅속 디그다 2022. 8. 3. 16:04

🤔 JPA 의 핵심 영속성 컨텍스트


JPA 의 핵심 2가지

  1. JAVA 의 객체와 관계형 데이터베이스 와의 Mapping 을 HOW?
  2. 영속성 컨텍스트 (1차 캐시)
    항상 날 괴롭게 한다..

영속성 컨텍스트 (persistence Context)

  • ❌ 영속성 컨텍스트 != 데이터 베이스 (임시 저장소라고 생각하자)
  • 논리적인 개념
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근
  • 엔티티 매니저당 하나의 영속성 컨텍스트를 가진다
  • J2EE 환경에서는 영속성 컨텍스트가 하나의 엔티티 매니저로 관리되는 것은 아니다

    같은 트랜잭션 안에서는 사용하는 EntityManger끼리 같은 영속성 컨텍스트를 공유

영속성 대상 (Entity) 의 상태

  1. 비영속 상태
  2. 영속 상태
  3. 준영속 상태
  4. 삭제

❗ 중요한 점은 영속 상태이다. 마치 database 에서 autocommit 을 해제하고 세션 별로 메모리를 할당 받는 내용과 비슷하다.

해당 기술을 통해 다음과 같은 이점들을 얻을 수 있게 된다.

하나씩 살펴 보자


1차 캐시

영속성 컨텍스트는 내부적으로 1차 캐시를 가지고 있다

(해쉬 맵이라고 생각 하면 편하다. Pk 즉 id를 키로 가지고 value로 entity 실객체를 가진다)

❓ 1차 캐시의 주된 기능?

▶ DB의 힘을 거치지 않고도 해당 PK를 가진 Entity 조회를 가능케 한다.

해당 엔티티가 영속화가 되어있지 않다면 DB에서 직접 조회를 하여 1차 캐시에 key-value 쌍으로 저장한다.

⏰ 주의 해야 할점은 JPQL 의 기능이 아니다. 영속성 컨텍스트의 기능이다. 즉, em.find() 로만 지원이 된다.

코드로 살펴보자

🖥

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaShop");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin();
            Member member = new Member();

            em.persist(member);
         /**
          * hibernate_sequence 가져와서 id 값을 넣어줌
          * 
          * Hibernate:
          * call next value for hibernate_sequence
          */

            em.find(Member.class, member.getId());
         /**
          * 해당 id를 key값으로 가진 member 를 1차 캐시에서 조회한다.
          */

         //삭제 - em.remove(findMember)
            tx.commit();
         /**
          * 해당 트랜잭션 commit 된다 
          * 즉, member 객체가 등록된다.
          * 
          * insert
          * into Member
          * (city, name, street, zipcode, MEMBER_ID)
          * values
          * (?, ?, ?, ?, ?)
          */

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

        emf.close();
    }
}

🤪 JPA의 장점

영속 엔티티의 동일성 보장

1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 어플리케이션 차원에서 제공한다,

❓ 반복가능한 읽기(Repeatable Read) 로써 다른 DB 세션에서 데이터 베이스를 update delete 해도 현재 사용하는 세션에는 영향을 미치지 않음

🤫 추가적으로 JPQL 차원에서 JPQL 이 전송될 때, dirty checking 및 insert 문들이 다 전송되기 때문에 repeatable read를 보장

이부분에서 말하는 동일성 보장의 경우

em.find 로 가져온 혹은 영속성 컨텍스트에 등록된 entity 들의 경우 '==' 비교가 가능하다. (같게 나온다 id가 같을 경우)

🖥

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
Assert a == b (True 보장)

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

주요 개념 : 쓰기 지연 SQL 저장소

❓ 쓰 지연 SQL 저장소?

영속성 컨텍스트와 DataBase 의 정합성을 맞추는 과정에서 필요한 SQL 들의 모음 집합이라고 생각하자

👍 중요한 기능이 나오는데 Batch 기능이 바로 그것이다.

쓰기 지연 저장소 사용시 batch insert가 가능해진다.

물론 DataBase 의 종류 마다 Generate Value 의 종류가 다르기 때문에 설정을 잘해야 한다.
트랜 잭션 커밋후에 데이터베이스에 insert, update SQL 이 전송된다. (엄밀히 말하면 commit 전의 flush이다.)

dirty checking

JPA 는 영속 상태인 entity를 관리할 때 snap shot 개념을 살려 변경 감지를 하는 dirty checking을 한다.

persistContext는 지켜 보고 있다가 flush 시점에 스냅샷과 현재 entity를 비교하여 update 문을 전송한다.

em.update() 이런 명령어 필요가 없다.

🤔 JPA 3.5 부터는 엔티티의 모든 필드를 업데이트 하게 된다. (변경된 필드만 업데이트를 하는 것이 아님) 

모든 필드를 업데이트 하면 수정 쿼리가 항상 같기 때문에 (형태가) 수정쿼리를 재사용 할수 있다
데이터 베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용 할 수 있다.
만약 변경된 엔티티만 업데이트를 하고 싶다면 org.hibernate.annotations.DynamicUpdate 어노테이션 활용

remove 의 사용

em.remove 를 사용하여 commit 시점에 delete 문이 전송되게 된다.


✅ FLUSH

데이터 베이스와 영속성 컨텍스트의 동기화 라는 개념으로 생각하면 편하다.

발생시 일어나는 일은 다음과 같다.

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

❓ 호출 조건

  1. em.flush - 직접 호출 (spring 과 혼합시 거의 사용 X)
  2. 트랜잭션 커밋 시점 - 자동 호출
  3. JPQL 쿼리 실행 - 자동 호출

❌ flush 를 호출 한다고 영속성 컨텍스트 초기화가 일어나는 것이 아니다 따로 em.clear()가 존재한다.

🤔 JPQL 에서 flush 를 하는 이유?

간단하다 DB와 영속성 컨텍스트의 동기화를 해야지 올바르게 동작이 이루어진다. 물론 실제 디비에 반영이 되는 것은 commit 시점 이후이다.

플러시 모드 옵션

  • FlushModeType.AUTO - 커밋이나 쿼릴를 실행할 때 플러시 (기본값)
  • FlushModeType.COMMIT - 커밋할 때만 플러시

생각정리

결국 db는 트랜잭션 단위로 자원을 관리하고 db를 병합을 한다.

트랜잭션을 열고 commit 을 하기 전에는 각 세션별 임시 저장소에서 데이터를 관리하다가 commit 후에 병합을 한다는 이야기다

JPA 차원에서는 굳이 세션별 임시 저장소를 가질 필요 없이 어플리케이션 레벨에서 캐싱을 하는 것이 훨씬 이점이 많게 되므로
영속성 컨텍스트를 사용하게 되는 것이다.


영속 상태로의 전환

  1. em.find()
  2. JPQL

find()

  1. 영속성 컨텍스트의 1차 캐시를 조회
  2. 존재하지 않을 경우 db에 select 쿼리 전송
  3. 결과를 영속성 컨텍스트에 등록

JPQL

    1. 무조건 db에 쿼리 전송
    2. 결과들을 영속성 컨텍스트에 등록
      ❗ 만일 결과물들이 영속성 컨텍스트에 존재 한다면 JPQL 로 가져온 결과를 버리고 영속성 컨텍스트에 존재하는 데이터 유지

기존 엔티티를 새로 검색한 엔티티로 대체했을 때 기존 엔티티가 수정중에 있다면 아주 큰 문제

물론 JPA가 Repeatable Read을 지원하므로 이것은 당연한 이야기다

 

위에 것에 예를 들어보면

🤔 내가 현재 트랜잭션에서 작업을 수행하다 다른 transaction 에서 update가 commit이 되었을 경우 해당 update 를 가져오는 것이 아니라 일관 되게 현재 데이터를 유지하는 것을 알 수 있다 고로, 다시 한번 JPA는 중간에 영속성 컨텍스트를 두어 Application 차원에서 Repeatable Read를 보장하는 것을 알 수 있다.

준영속 상태로의 전환

  1. em.detach()
    특정 엔티티만 준 영속 상태로 전환
  2. em.clear()
    영속성 컨텍스트를 완전히 초기화

  3. em.close()
    영속성 컨텍스트를 종료

✅ 준영속 상태로 전환 했을 경우

  • 1차 캐시에서 조회 불가
  • dirty checking 불가
  • 지연로딩 사용불가

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

프록시와 연관관계 관리  (0) 2022.08.23
고급 매핑  (0) 2022.08.23
JPA - 연관관계 이해하기  (0) 2022.08.03
JPA - 엔티티 매핑  (0) 2022.08.03
JPA 시작  (0) 2022.08.03
댓글
11-08 10:33
Total
Today
Yesterday
링크