강의 링크
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런
영속성 컨텍스트
- JPA를 이해하는데 가장 중요한 용어, 논리적인 개념이다. (눈에 보이지 않는다)
- 엔티티를 영구 저장하는 환경.
- EntityManager를 통해 영속성 컨텍스트에 접근한다
- EntityManager와 영속성 컨텍스트가 1:1 매핑을 한다.
- EntityManager.persist(entity)
- persist = 영속(지속하다)
- 엔티티를 DB가 아니라 “영속성 컨텍스트”에 저장한다는 의미.
엔티티의 생명주기
- 비영속 (new/transient)
- transient - 과도 현상 : 정상 상태가 될 때까지의 사이에 생기는 현상.
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속 (managed)
- em.persist(member); 이때 바로 데이터 베이스에 저장되는 것이 아니다!
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); System.out.println("====BEFORE====") em.persist(member); // System.out.println("====AFTER====")
- 영속성 컨텍스트에 관리되는 상태
- 준영속 (detached)
em.detach(member)
- 영속성 컨텍스트에 저장되었다가 분리된 상태. (아무 관계가 없어지는 상태)
- 삭제 (remove)
- 삭제된 상태
영속성 컨텍스트의 이점
- 1차 캐시
- 동일성(identiy) 보장
- 트랜잭션을 지원하는 쓰기 지연 (transcational write-behind)
- 변경 감지 (Dirty Checking)
- 지연 로딩 (Lazy Loading)
1차 캐시에서 조회
- find(조회)시 DB 접근으로 가져오는 것이 아니라 1차로 캐시에서 가져온다.
- 1차 캐시에 없다면 → DB에서 조회 후 → 1차 캐시에 저장한다.
System.out.println("====BEFORE===="); em.persist(member); // System.out.println("====AFTER===="); Member findMember = em.find(Member.class , 101L); System.out.println("findMember.id = " + findMember.getId()); System.out.println("findMember.namw = " + findMember.getName());
- 먼저 1차 캐시에서 값을 찾고 쿼리문이 나가는 것을 확인할 수 있음.
Member findMember1 = em.find(Member.class , 101L); Member findMember2 = em.find(Member.class , 101L);
- 똑같은 엔티티 조회시 쿼리문이 1번만 작용하게 된다.
- 1차 캐시에 없다면 → DB에서 조회 후 → 1차 캐시에 저장한다.
- 영속성 컨텍스트는 하나의 트랜잭션안에서 작용된다. 따라서 트랜잭션이 끝나면 1차 캐시도 삭제되게 되므로 사실 큰 성능 향상을 주지는 않는다.(여러명이 공유하는 캐시 - 2차 캐시)
영속 엔티티의 동일성 보장
Member findMember1 = em.find(Member.class , 101L);
Member findMember2 = em.find(Member.class , 101L);
System.out.println(findMember1 == findMember2); // true
- 1차 캐시로 Repeatable read 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.
엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연
- persist가 동작하면 쓰기 지연 데이터베이스와 1차 캐시에 데이터를 저장한다.
- 이후 commit이 동작할 때 그때 DB에 query를 날려서 저장한다!
- 이를 통해 persistence에 코드를 추가하면 버퍼링 기능을 수행할 수 있게 된다.
<property name="hibernate.jdbc.batch_size" value="10"/>
엔티티 수정 - 변경감지(Dirty Checking)
Member member1 = em.find(Member.class, 150L);
member1.setName("ZZZZZZ");
- persist를 사용할 필요는 없다 → Java collection 다루듯이 값만 변경해도 JPA가 이를 감지하고 UPDATE 쿼리를 보내게된다.
- 따라서 if문을 사용하려고 해도 JPA는 이미 setName을 통한 변경감지로 UPDATE 쿼리가 전송될 것이다.
- 데이터 수정 시 if문을 사용하는 것은 무용지물.
- 변경 감지 과정
- 1차 캐시에 SnapShot을 만든다.
- SnapShot : 값을 읽어온 최초 시점의 상태를 저장하는것
- SnapShot과 변경된 값이 다르면 이를 감지하고 (변경 감지) Update 쿼리를 전송한다.
- 1차 캐시에 SnapShot을 만든다.
플러쉬 - flush
- 영속성 컨택스트의 변경내용을 데이터베이스에 적용.
- 사용 방법
- em.flush() - 수동
- 트랜잭션 커밋 - 자동
- JPQL 쿼리 실행 - 자동
- 과정
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리들 flush가 작동하면 데이터 베이스의 쿼리를 데이터 베이스에 전송한다.
- 대체적으로 자동으로 JPA에서 호출하게된다.
- 수동으로 사용할 경우
- 쿼리를 중간에 확인하고 싶거나.
- 강제적으로 중간에 쿼리를 보내고 싶거나.
- 수동으로 사용할 경우
- flush를 해도 1차 캐시가 유지된다.
- JPQL 쿼리 실행하면 왜 자동으로 실행될까?
쿼리가 날라가지 않은 상태에서 JPQL을 사용해 조회하면 아무것도 조회할 수 없을 것이기 때문에 JPQL을 사용하면 자동적으로 flush가 실행된다.
- 영속성 컨텍스트를 비우지 않는다!!!
- 트랜잭션의 작업 단위가 중요 → 커밋 직전에만 동기화 하면 된다.
준영속 상태
- 영속 → 준영속이 된다면…
- em.persist() (영속 상태가 됨) , em.find(class,id) (1차 캐시를 업데이트하면서 영속상태가됨)
- 준영속 상태가 되면 해당 컨텍스트에서의 영속상태를 해제한다
- 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
- commit해도 아무런 변화가 일어나지 않는다.
- 사용 방법
- em.detach(entity) - 특정 엔티티만 준영속 상태로 전환
- em.clear() - 영속성 컨텍스트를 완전히 초기화
- em.close() - 영속성 컨텍스트를 종료