[JPA] 기본 매핑과 영속성 관리

hi·2022년 9월 23일

매핑

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리한다
  • 기본 생성자 필수 (public, protected)
    (내부에서 동적으로 객체를 생성하기에 필요 프록싱 등..)
  • final 클래스, enum, interface, inner 클래스 사용 X
  • 저장할 필드에 final 사용 X
  • name 속성

@ID

데이터베이스 PK와 매핑


EntityManagerFactory

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
  • hello => persistence.xml의 <persistence-unit name="hello"> 에서 읽어와 생성
  • 로딩 시점에 하나만 생성하여 애플리케이션 전체에서 공유한다

EntityManager

EntityManager em = emf.createEntityManager();
  • 트랜잭션 단위마다 생성
  • 쓰레드간에 공유 X , 사용하고 버려야 한다
  • 종료시 영속성 컨텍스트, 1차캐시도 함께 사라짐

고객의 요청이 오면 엔티티매니저팩토리는 엔티티매니저를 생성
-> 엔티티매니저는 내부적으로 DB 커넥션을 사용하여 DB를 사용

EntityTransaction

EntityTransaction tx = em.getTransaction(); 트랜잭션 얻기

tx.begin(); 트랜잭션 시작

em.persist(); 저장

  • 1차캐시에 저장되며 스냅샷을 보관, Entity를 분석하여 SQL 생성 -> 쓰기지연 저장소에 쌓아둠.
    commit() 실행시 flush되며 쿼리 실행 -> DB 커밋

em.find(); 조회

em.remove(); 삭제

em.set~(); 수정

  • 수정 후 따로 저장하지 않음
  • JPA를 통해 Entity를 가져오면 JPA가 관리
  • 커밋 시점에 변경이 됐는지 모두 체크하고,
    변경되었다면 update 쿼리를 직접 만들어 날리고 커밋

tx.commit();

  • 내부적으로 flush() 호출 -> Entity와 스냅샷을 비교 -> 값이 변경되었다면 update 쿼리를 쓰기지연 저장소에 저장 -> 커밋

tx.rollback();

em.close();
emf.close(); 닫기


🔎
  • 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다
  • persist 실행시 바로 DB에 저장되지 않고 쿼리문을 생성해뒀다가 커밋 시점에 보낸다

JPQL

테이블이 아닌, entity 객체를 대상으로 쿼리 사용

ex) em.createQuery("select m from Member as m", Member.class)

batch size

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

모았다가 한번에 쿼리 보내는 개수 설정


영속성 컨텍스트

  • Entity를 영구 저장하는 환경으로 논리적인 개념
  • EntityManager를 통해 접근
    엔티티매니저는 트랜잭션 단위로 생성, 종료되므로 영속성 컨텍스트도 함께 사라짐
  • 1차캐시, 쓰기지연 SQL 저장소가 있음

엔티티 생명주기

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

  • new

영속 상태
영속성 컨텍스트에 관리되는 상태
1차 캐시에 올라간 상태

  • em.persist()
    em.find()

준영속 상태
영속성 컨텍스트에 저장되었다가 분리된 상태
영속성 컨텍스트가 제공하는 기능 사용 불가

  • 새로운 객체여도 이미 DB에 저장되었던 기존의 식별자를 갖고 있다면 준영속 엔티티

  • em.datach() 특정 엔티티만 준영속 상태로 전환
    em.clear() 영속성 컨텍스트 초기화
    em.close() 영속성 컨텍스트 종료

삭제
삭제된 상태

  • em.remove

영속성 컨테이너의 이점

1차 캐시

  • persist() 실행시 1차 캐시에 저장되고 커밋시 DB에 저장
  • find() 실행시 1차 캐시에 없는 경우 DB에서 조회하고, 1차 캐시에도 저장된다 -> 이후 바로 1차 캐시에서 조회됨
  • DB에서 조회하면 무조건 영속성 컨텍스트(1차 캐시)에 올라감
  • 애플리케이션 전체에서 공유(여러 고객이 함께 사용)하는 캐시는 2차 캐시라 함

동일성(identity) 보장

Member a = em.find(Member.class, "member1"); //JPA가 DB에서 가져오면서 1차 캐시에 올려둠 , 쿼리문 o
Member b = em.find(Member.class, "member1"); //1차캐시에서 가져옴 , 쿼리문 x
System.out.println(a == b); //true => 동일성 보장(1차 캐시 때문에 가능) but, 같은 트랜잭션 안에서

트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

em.persist(memberA);
em.persist(memberB); //아직 쿼리문을 보내지 않음

transaction.commit(); //커밋시 보냄

변경 감지(Dirty Checking)

  • 영속 엔티티를 조회하고 수정 후 따로 저장하는 코드 불필요
  • 커밋 시점에 flush()가 발생되는데 , 엔티티와 스냅샷을 비교한다
    (1차 캐시에 들어온(값을 읽어온) 최초의 데이터를 스냅샷으로 생성해둠)
    변경되었다면 update 쿼리를 쓰기 지연 SQL 저장소에 생성 -> 반영

flush()

영속성 컨텍스트의 변경내용을 DB에 동기화
보통 트랜잭션이 커밋되면 자동으로 flush 발생

flush 발생시

  • 변경 감지
  • 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송
  • 영속성 컨텍스트를 비우지 않음

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

직접 호출 방법 : em.flush()

자동 호출 방법 :

  • 트랜잭션 커밋
  • JPQL 쿼리 실행
    • DB에서 값을 가져오려면 미리 반영되어야 하기에
    • 이때 실행될 JPQL과 관련된 엔티티만 부분적으로 flush

0개의 댓글