3 - JPA 영속성 관리

dropKick·2020년 7월 30일
0

Persistence Context

  • 엔티티 매니저를 통해 엔티티들이 생성
  • 엔티티를 보관하고 관리할 가상의 컨테이너가 필요

엔티티 생명주기

  • 비영속
    순수한 엔티티 객체, 관련 없음
  • 영속
    엔티티 객체가 영속성 컨텍스트에 저장된 상태, 컨텍스트에 의해 관리
  • 준영속
    영속성 컨텍스트에 의한 관리가 풀린 엔티티

Persistence Context

  • 엔티티를 식별자 값(PK) 매핑으로 구분
  • Managed Entity들은 모두 KEY-VALUE 식별자 값이 존재(없을 시 예외 발생)
  • 엔티티들은 엔티티 컨텍스트로 저장될 때가 아닌 트랜잭션 커밋 시 데이터베이스로 넘어가는데 이를 flush()로 사용하고 쓰기 지연이라고 한다.

    flush는 엔티티를 지우는 것이 아닌 DB와 동기화 시키는 것

1차 캐시, 동일성 보장

// 엔티티 생성(비영속)
Memeber member = new Member();
member.setId("member1"); // KEY @Id
member.setUsername("user1");

// 엔티티 영속
em.persist(member); 

// 1차 캐시 내 엔티티 반환
Member member1 = em.find(Member.class, "member1"); 

// 1차 캐시 내 없는 엔티티 반환
// 새 엔티티 생성, 1차 캐시 저장 후 반환
Member member2 = em.find(Member.class, "member2");

// 동일성 보장
Member member3 = em.find(Member.class, "member1");
System.out.print(member1 == member3); // true


  • 1차 캐시 내 엔티티를 조회하기 때문에 동일 엔티티 취급
  • 성능 상 이점을 누릴 수 있다고 되어있으나 사실 상 미미

Managed Entity(영속)

  • em.persist()를 통해 엔티티 컨텍스트에 저장된 엔티티
  • 영속성 컨텍스트에 의해 쓰기 지연, 지연 로딩의 효과를 볼 수 있음

쓰기 지연 (Write-Behind)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 반드시 트랜잭션 수행
transaction.begin(); // 시작

// 영속 상태일뿐 Insert 된 데이터가 아님, 1차 캐시에 존재
em.persist(memberA);
em.persist(memberB); 

transaction.commit(); // 커밋(종료) 


  • 엔티티 작업 자체가 트랜잭션 단위로 존재
  • 트랜잭션이 커밋 되기 전까지 엔티티는 실제 반영되지 않고 1차 캐시 내 존재
  • 트랜잭션 커밋 전 엔티티의 수정이 자유로움

변경 감지 (Dirty-Chekcing)

쓰기 지연으로 엔티티 수정이 자유로운 이유는 이 변경 감지에 있음
(더티 체킹이라고도 많이 부름)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

transaction.begin()

// 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

// em.update(member); 변경 감지로 인해 JPA에 존재하지 않는 Update SQL

transaction.commit(); // 이때 스냅샷과 비교 후 Update 쿼리 날림
  • 쓰기 지연으로 인해 트랜잭션 커밋 전에는 엔티티는 모두 쓰기 지연 저장소에 있음
    따라서 엔티티의 수정은 엔티티 자체를 수정하면 됨
  • 이런 변경 감지는 스냅샷을 이용해서 구현 되는데 JPA는 맨 처음 조회 시 엔티티 스냅샷을 생성하고, 스냅샷과 비교를 통해 변경을 감지함
  • 트랜잭션 종료 시 flush()를 통한 DB 쓰기가 발생
    생성된 스냅샷과 현재 엔티티를 비교하고, 엔티티 변경이 확인되면 Update 쿼리를 생성
    아직 쓰기 지연 저장소에 엔티티가 있기때문에 쓰기 지연 쿼리들과 함께 DB 쓰기
    결국 엔티티 데이터는 수정 됨

참고사항

아직 잘 모르겠는 부분이지만 나중에 쓸 것 같아서..

  • 더티 체킹은 테이블의 모든 필드가 업데이트 됨
    테이블 정규화가 잘못 되어있다면 그만큼의 성능 하락을 불러오지만 장점도 있음
    • 생성되는 쿼리가 같아 부트 실행시점에 미리 만들어서 재사용가능합니다.
    • 데이터베이스 입장에서 쿼리 재사용이 가능하다
    • 동일한 쿼리를 받으면 이전에 파싱된 쿼리를 재사용한다.
  • 더티 체킹 시 변경 부분만 업데이트 하기

Detached Entity(준영속)

  • 엔티티 컨텍스트에서 분리된 상태의 엔티티

준영속 상태를 굳이 왜 만드는걸까?

준영속 상태는 1차 캐시, 트랜잭션 지연, 지연 로딩 등 어떤 관리도 못받는데 왜 남겨두는걸까라고 생각 했는데 그 이유는 이렇다고 한다.

  • 엔티티를 조회했는데, 트랜잭션이 끝나버리고 영속성 컨택스트도 사라지면 그때 조회한 엔티티가 준영속 상태가 됩니다. 이런 경우는 사실 실무에서 많이 발생합니다.

  • 비영속 상태와 별 다를것 없지만 하나 확실한 것은, 실존하는 데이터라는게 증명이 된다는 것이다.
    (영속성 컨텍스트에 들어갔었으면 등록되거나, 조회되어진 데이터이므로)
    실제로 개발자가 준영속 상태를 활용할 경우는 거의 없다.

  • 즉 개발자가 직접 준영속 상태를 만드는 경우는 거의 없다.
    영속성 컨텍스트가 종료 되면서 엔티티가 준영속 상태가 되는 경우가 대부분이고 이런 경우
    API 성능 최적화를 위한 해결이 필요하다.

  • 준영속 상태일 때 지연로딩을 할 수 있는 방법

엔티티 동기화 flush()

기본적으로 flush는 트랜잭션 종료 시 자동 호출 됨
그런데 특이하게 트랜잭션이 종료되지 않은 상태에서 JPQL 쿼리를 실행할 때도 호출 됨

// 트랜잭션 시작
em.persist(A);
em.persist(B);
em.persist(C);

// JPQL
List<Member> members = em.createQuery("SELECT m FROM Memmber m", Member.class).getReuslt();

// 트랜잭션 
  • JPQL 실행 시점에 트랜잭션이 아직 종료되지 않아 DB Insert 되지 않은 상태
  • 하지만 JPQL은 쿼리 실행 결과를 받을 수 있는데, 이는 JPA가 JPQL을 실행 시 플러시를 호출하기 때문임

0개의 댓글