영속성 관리 - 내부 동작 방식

slee2·2022년 2월 21일
0

영속성 컨텍스트

JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기
  • 영속성 컨텍스트

엔티티 매니저 팩토리, 엔티티 매니저

웹 어플리케이션을 개발한다면, 엔티티 매니저 팩토리를 통해서 고객에게 요청이 올때마다 엔티티 매니저를 생성한다.
엔티티 매니저는 데이터 커넥션을 사용해서 DB를 사용하게 된다.

영속성 컨텍스트란?

  • JPA를 이해하는데 가장 중요한 용어
  • "엔티티를 영구 저장하는 환경"이라는 뜻
  • EntityManager.persist(entity);
  • 논리적인 개념
  • 눈에 보이지 않는다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

엔티티의 생명주기

  • 비영속(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);

DB에 저장되는 시점은 커밋 이후

준영속, 삭제

//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);

//객체 삭제
em.remove(member);

detach의 경우에는 DB에는 값이 남아있고, 영속성 컨텍스트에는 남아있지 않고,
remove의 경우에는 DB에도 값을 지우는?

영속성 컨텍스트의 이점

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

1차 캐시

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

em.persist(member);

1차 캐시 조회

//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

데이터베이스에서 조회

Member findMember2 = em.find(Member.class, "member2");

영속 엔티티의 동일성 보장

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

System.out.println(a == b); // 동일성 비교 true

1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

이와 관련하여, 캐시 덕분에 동일성이 보장된다는 말을 보고
그럼 데이터베이스에서 받아오는건 동일성이 보장안될까? 가 궁금해져 작은 실험을 함.

DB 쿼리로 보내서 값을 가져온건 동일할까?

동일하다.

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

쉽게 말해 em.persist()를 한다고 해서 바로 데이터베이스에 쿼리를 날리지 않음.
트랜잭션 커밋까지 해야 통째로 보냄.


변경 감지(Dirty Checking)

쉽게 말해 트랜잭션이 커밋했을때, 값이 바뀐게 있다면, 자동으로 변경해줌.

자세히 말하자면, 최초로 (데이터베이스에서 값을 가져왔건, find로 찾았건간에)만들어진 엔티티를 스냅샷에 저장한다.
그 후에 만약 값이 변경했다면, Entity에 변경된 값이 저장되어있을 것이다.
트랜잭션이 커밋할 때, flush가 호출되면서 그때 JPA가 Entity와 스냅샷을 전부 비교하여 바뀐 곳이 있다면, SQL에 Update를 만들어둬서 데이터베이스에 커밋하게 된다.

엔티티 삭제

Member memberA = em.find(Member.class, "memberA");

em.remove(memberA); // 엔티티 삭제

플러시

  • 영속성 컨텍스트의 변경내용을 데이터베이스에 반영

플러시 발생

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

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

  • em.flush() - 직접 호출
  • 트랜잭션 커밋 - 플러시 자동 호출
  • JPQL 쿼리 실행 - 플러시 자동 호출

플러시 이후에도 1차 캐시 값들은 남아있다.

JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

// 커밋, 플러시를 따로 안했기 때문에 아직 데이터베이스로 저장이 안됨.

// 중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.calss);
// 데이터를 아직 저장하지 않아서 위 세 맴버 조회 안됨.

이러한 것들 때문에 문제가 발생할 수 있어서 JPQL 쿼리를 실행하게 되면 무조건 플러시가 자동호출된다.
그러면 중간에 JPQL을 실행하고 맴버 ABC가 조회된다.

플러시 모드 옵션

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

거의 사용할 일 없다. 커밋을 굳이 음 그렇다.

플러시는

  • 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨

준영속 상태

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

준영속 만드는 법

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

detach를 쓰게 되면 이후에 set을 통해 내용을 바꿔도 변경감지가 안됨.
(엔티티 기능이 적용 안됨)

clear의 경우 1차 캐시의 내용을 전부 날리기 때문에 요청을 새로 한다.
그렇기 때문에 테스트할때 눈으로 영속성 컨텍스트에서 요청하는 것을 다시금 보고 싶을때 쓰인다고 한다.

0개의 댓글

관련 채용 정보