[JPA] 03. 영속성 관리

매빈·2023년 5월 28일
0

JPA-programming

목록 보기
3/7
post-thumbnail

수강 인증

실습 코드는 Github에!

JPA에서 가장 중요한 2가지

  • 객체와 RDS 매핑
  • 영속성 컨텍스트: 실제 JPA가 내부에서 어떻게 동작하는가

3.1 엔티티 매니저 팩토리와 엔티티 매니저


엔티티매니저 팩토리에서 요청이 들어올 때마다 엔티티 매니저를 각각 만들어내고, 만들어진 엔티티 매니저들은 내부적으로 데이터베이스 커넥션을 이용하여 DB를 사용함

3.2 영속성 컨텍스트란?

  • JPA를 이해하는데 가장 중요한 용어
  • JPA를 영구 저장하는 환경
  • EntityManager.persist(entity) : 엔티티 매니저를 사용하여 엔티티를 영속화한다 즉, 엔티티를 영속성 컨텍스트에 저장함

영속성 컨텍스트는 논리적인 개념으로, 눈에 보이지 않음. 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있음

➡️ 엔티티 매니저 속에 (눈에 보이지 않는) 영속정 컨텍스트가 생성됨

3.3 엔티티의 생명주기

  • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 영속(managed): 영속성 컨텍스트에 관리되는 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

1) 비영속

// 객체를 생성한 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
  • 객체를 생성만 해둔 상태
  • JPA와는 전혀 관계 없음

2) 영속

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

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

// 객체를 저장한 상태(영속)
em.persist(member);
  • 이때, 영속 상태라고 해서 db에 저장되는 것은 아니다.
System.out.println("BEFORE");
em.persist(member);
System.out.println("AFTER");

다음과 같이 코드를 작성해서 실행해본 결과, 아래와 같이 BEFORE과 AFTER 사이에 sql 쿼리문이 print되지 않았다.

  • 그럼 언제 db에 저장이 되는가? ➡️ 트랜잭션을 커밋하는 시점에! (tx.commit())

3) 준영속

영속 상태로 전환하기 위해서는 em.persist() 사용하면 되는데, 다른 한 가지 방법이 더 있음. 바로 1차 캐시에 없을 때 DB에서 찾아서 1차 캐시에 넣는 방법 (앞에서 정리했던)

  • 준영속상태란, 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)되는 것
  • 영속성 컨텍스트가 제공하는 기능을 사용하지 못함.
  • 준영속 상태 전환 방법
    • detached(entity): 특정 엔티티만 준영속 상태로 전환
    • em.clear(): 영속성 컨텍스트를 완전히 초기화
    • em.close(): 영속성 컨텍스트를 종료

3.4 영속성 컨텍스트의 특징

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

1) 1차 캐시

  • 영속성 컨텍스트는 내부에 1차 캐시를 두고 있음.
  • 엔티티를 영속하게 되면, 내부에 1차 캐시가 생성됨. (사실상 1차 캐시를 영속성 컨텍스트라고 이해해도 됨.)
  • 영속 컨텍스트에서 em.find(Member.class, "member1")을 통해 찾으면 db를 조회하는 것이 아니라 1차 캐시를 조회함
  • 만약, 1차 캐시에 존재하지 않는 데이터를 조회하면 JPA가 DB를 조회하고, DB에 해당 데이터에 있다면 그 데이터를 1차 캐시에 저장한 후 반환함
    이때, DB에 있는 데이터를 조회해야 함(당연하다)

db에는 존재하지만 1차 캐시에 존재하지 않는 데이터를 조회하면 터미널에 아래와 같은 select 쿼리가 입력된다.

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

2) 영속 엔티티의 동일성 보장

  • 위와 같이 서로 같은 엔티티를 조회하는 코드를 작성하고 서로 같은지 비교해봤을 때, true 값을 반환한다.
  • 1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공하기 때문.

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

  • 영속 컨텍스트 내부에는 쓰기 지연 SQL 저장소가 존재함.
  • memberA가 들어가면, JPA 가 엔티티를 분석해서 insert 쿼리를 생성후 저장소에 넣어둠. 다른 엔티티(ex. memberB)가 들어갈 때도 마찬가지로 쿼리 생성 후 저장소에 넣어둠.
  • 저장소에 저장된 엔티티들은 트랜잭션을 커밋하는 시점에 flush되면서 실제 DB 쿼리로 실행됨

4) 엔티티 수정시 변경 감지

  • commit하는 시점에 flush가 이루어지고, JPA가 엔티티와 스냅샷을 비교하게 됨. 이때, 다른 것이 감지되면 update sql 쿼리를 생성하고 해당 쿼리를 DB에 반영함.

    1차 캐시 안에는 스냅샷이라는 컬럼이 존재한다. 엔티티가 최초로 영속성 컨텍스트에 저장될 때의 모습을 남겨놓은 것이라 생각하면 됨

3.5 플러시(flush)

  • 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것

1) 플러시 발생

  • 변경 감지
  • 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송(등록, 수정, 삭제 쿼리)

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

  • em.flush: 직접 호출 (직접 쓸 일은 거의 없지만, 테스트 할 때를 대비해 알아두기)
  • 트랜잭션 커밋: 플러시 자동 호출
  • JPQL 쿼리 실행: 플러시 자동 호출

3) 플러시 모드 옵션

  • 사실 쓸 일은 없음.. 가급적 손대지 말고 auto 사용하기
  • FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본값)
  • FlushModeType.COMMIT: 커밋할 때만 플러시

정리

  • 플러시는 영속성 컨텍스트를 비우는 것이 아니라, 변경 내용을 DB에 동기화하는 것!
  • 매커니즘이 동작할 수 있는 이유는 트랜잭션이 있어서 가능한 것. 따라서 어떻게 뭘 하든 commit 직전에만 변경 내용을 DB에 동기화시켜주면 됨.

0개의 댓글