영속성 컨텍스트, 플러시, 준영속 상태

Gyeongjae Ham·2023년 5월 19일
0

JPA

목록 보기
3/12
post-thumbnail

해당 시리즈는 김영한님의 JPA 로드맵을 따라 학습하면서 내용을 정리하는 글입니다

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어입니다
  • 엔티티를 영구 저장하는 환경이라는 뜻입니다
  • EntityManager.persist(entity);
  • EntityManager안에 영속성 컨텍스트가 있다고 생각하면 됩니다

엔티티의 생명주기

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

비영속

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

영속

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

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

// 객체를 저장한 상태(영속)
em.persist(member);
  • 영속 상태가 된다고 해서 em.persist(member);에서 바로 DB로 쿼리가 날아가는게 아닙니다
  • 쿼리는 transaction을 커밋하는 시점에 날아갑니다

준영속

  • 영속성 컨텍스트에서 객체를 지운 상태입니다
// 회원 엔티티를 영속성 컨텍스트에서 분리(준영속)
em.detach(member);

삭제

  • DB에 삭제를 요청한 상태입니다
// 객체를 삭제한 상태(삭제)
em.remove(member);

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

1차 캐시

  • 영속성 컨텍스트 내부에는 1차 캐시 공간이 있습니다

  • Map의 구조로 DB PK를 키로 하고 객체를 값으로 가지고 있습니다

  • 이렇게 1차 캐시를 가지면, 조회시에 캐시를 먼저 찾아보는 이점을 가질 수 있습니다

    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
    
    // 1차 캐시에 저장됨
    em.persist(member);
    
    // 1차 캐시에서 조회
    Member findMember = em.find(Member.class, "member1");

  • 1차 캐시에 없는 객체를 조회하는 경우는 어떻게 될까요?

    • 먼저 1차 캐시에서 객체를 조회해봅니다
    • 1차 캐시에서 없음을 확인하고 DB에서 조회를 합니다
    • 그리고 DB에서 조회한 객체를 1차 캐시에 저장한 후에 결과를 반환합니다
  • 물론 영속성 컨텍스트는 트랜잭션 단위로 생성되므로 사용후에 사라진다는 점에서 크게 캐싱 능력이 도움이 되지 않을 수도 있지만 규모가 크다면 의미 있는 성능 향상이 있을 수 있습니다

영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b); // 동일성 비교 true
  • 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공합니다(같은 트랜잭션 내에서 비교해야 합니다)

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

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
tx.begin(); // [트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다

// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다
transaction.commit(); // [트랜잭션] 커밋

  • memberA를 저장하면 1차 캐시에 memberA를 저장하면서 동시에 영속성 컨텍스트 내부에 있는 쓰기 지연 SQL 저장소JPA가 분석한 memberA에 대한 INSERT 쿼리를 쌓아둡니다
  • memberB의 경우에도 같은 동작이 수행됩니다
  • 트랜잭션을 커밋하는 시점에 쌓여있는 쿼리문들이 DB에 날아가게 됩니다(flush) 그 후 트랜잭션이 커밋됩니다

엔티티 수정 시 변경 감지

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

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

// 영속 엔티티 데이터 수정
memberA.serUsername("hi");
memberA.serAge(10);

// em.update(member); 같은 코드가 필요해 보이지만 자동으로 감지해서 SQL문을 생성해준다
tx.commit();
  • JPA의 목적이 데이터를 객체의 컬렉션을 다루듯이 사용하는 것처럼, 값을 변경하는 시점에서 작업이 끝나도록 기능이 만들어져 있습니다
  • JPA는 트랜잭션이 커밋되는 시점에 flush가 호출됩니다. 이 때 JPA는 1차 캐시안의 엔티티와 스냅샷(값을 읽어오는 시점의 엔티티의 최초 상태를 말합니다)을 비교합니다
  • 그리고 변경점이 생긴걸 감지했을 경우에 UPDATE 쿼리를 쓰기 지연 SQL 저장소에 저장합니다
  • 그 후 쌓여있던 쿼리들이 DBflush되고 트랜잭션이 커밋됩니다

엔티티 삭제

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

em.remove(memberA); // 엔티티 삭제
  • 트랜잭션이 커밋되는 시점에 DB에서 데이터를 삭제합니다

Flush(플러시)란 무엇인가요?

  • 영속성 컨텍스트의 변경내용을 데이터 베이스에 반영하는 동작을 의미합니다
  • 플러시가 된다 해도 1차 캐시의 내용은 유지됩니다
  • 플러시는 쓰기 지연 SQL 저장소에 있는 쿼리들이 DB에 반영이 되는 과정입니다

플러시 발생

  1. 변경을 감지합니다
  2. 수정된 엔티티에 대한 SQL 쿼리를 쓰기 지연 SQL 저장소에 등록합니다
  3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다(등록, 수정, 삭제 쿼리)

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

  • em.flush() - 직접 호출하는 방법

  • 트랜잭션 커밋 - 플러시 자동 호출되는 방법

  • JPQL 쿼리 실행 - 플러시 자동 호출되는 방법

    • JPQL 쿼리 실행 시 플러시가 자동으로 호출되는 이유
    em.persist(memberA);
    em.persist(memberA);
    em.persist(memberA);
    
    // 중간에 JPQL 실행
    query = em.createQuery("select m from Member m", Member.class);
    List<Member> members = query.getResultList();
    • persist 시점에는 쿼리가 날아가지 않으므로, JPQL 쿼리 실행 전에 자동으로 플러시되어 쿼리를 날려놔야 JPQL 쿼리가 의도에 맞게 동작할 것입니다

플러시 모드 옵션

em.setFlushMode(FlushModeType.COMMIT)

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

정리

  • 영속성 컨텍스트를 비우는게 아닙니다!!
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업입니다
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됩니다

준영속 상태

  • 영속 상태 -> 준영속 상태
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못합니다

준영속 상태로 만드는 방법

  • em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
  • em.clear() : 영속성 컨텍스트를 완전히 초기화
  • em.close() : 영속성 컨텍스트를 종료
profile
Always be happy 😀

0개의 댓글

관련 채용 정보