이 글은 김영한님의 JPA 강의 중 3장을 듣고 정리한 내용입니다 :)
강의 : 자바 ORM 표준 JPA 프로그래밍 - 기본편
교재 : 자바 ORM 표준 JPA 프로그래밍🤷♀️
이 장에서는 가장 중요한 2가지 중에 영속성 컨텍스트에 대해 알아보자-!
: 엔티티를 영구 저장하는 환경, 논리적인 개념
: 애플리케이션 - DB 사이에서 객체를 보관하는 가상의 DB 역할 (→ 플러시 시점에 DB에 반영)
엔티티매니저로 영속성 컨텍스트에 접근 (J2SE에서는 1:1관계로 생각, SpringBoot는 N:1)
이걸 쓰는 이유? 버퍼링이나 캐싱을 위해서 사용한다!
객체만 생성하고 엔티티와 연결X
객체를 생성하고 저장한 상태,
영속성 컨텍스트가 관리하는 엔티티(1차캐시/쓰기지연저장소에 있는 상태)
ex) em.persist(member);
//객체를 생성만 한 상태(비영속)
Member member = new Member();
member.setId("member1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
엔티티가 영속성 컨텍스트에서 분리 ex) em.detached( );
엔티티 삭제 ex) em.remove( );
엔티티 조회 시
이때, EM은 트랜잭션 단위이기에 트랜잭션이 끝나면 1차 캐시도 지워짐
⇒ 1차 캐시는 성능 향상도 있지만 매커니즘적인 장점이 큼
(참고로 2차 캐시란 애플리케이션 전체에서 공유하는 캐시이다)
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b) // 동일성 보장
같은 호출을 반복해도 1차 캐시에 있는 같은 엔티티 반환하여 동일성을 보장함
(JPA가 아닌 Mybatis는 동일성 보장이 안됨)
참고 - JPA 동일성 보장의 문제점?
(읽기 등급의 트랜잭션 격리 수준을 DB차원이 아닌 애플리케이션 차원에서 제공) → 16.1절에서
트랜잭션을 커밋하는 순간에 → 한 번에 디비에 SQL을 보냄
그 전에는 쓰기 지연 SQL 저장소에 모았다가 한번에 보냄!(성능을 위해서)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
수정할 때, 개발자가 따로 업데이트나 저장 안하는 게 맞음
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 없어도 된다!
transaction.commit(); // [트랜잭션] 커밋
이때 내부 작동 방식을 보자면,
플러시 시점에 엔티티와 스냅샷을 비교해서 바뀐 엔티티 찾는데
바뀐 엔티티가 있으면→ update 쿼리를 쓰기 지연 저장소에 저장 → 디비에 반영
: 영속성 컨텍스트의 변경내용을 DB에 동기화 (이름으로 오해X, 삭제 아님!)
⇒ 방법 : em.flush()로 직접 호출 or 트랜잭션 커밋 or JPQL
⇒ 1차 캐시랑은 상관 X, 쓰기지연 저장소를 한 번에 디비에 반영하는 과정임!
트랜잭션 커밋 직전에만 동기화하면 되니까 이런 매커니즘이 가능함
? 후에 추가 학습! ) 트랜잭션과 영속성 컨텍스트의 주기를 맞추는 게 중요함!
참고) 트랜잭션이란? DB의 상태 변화하는 작업 단위(단위는 개발자가 정함)
: 영속 상태의 엔티티가 영속성 컨텍스트에서 분리 em.detached( );
준영속 상태란, 1차 캐시랑 쓰기 지연 SQL 저장소에서 제거된 상태
⇒ 즉 트랜잭션 커밋할 때, 쿼리 생성이 안됨
영속상태란, JPA가 관리하는 상태, 1차 캐시에 있는 상태 ex) em.persist() , em.find( )
em.detach(entity)
: 특정 엔티티만 준영속 상태로 바꿈
em.clear()
: 1차 캐시 지우기, 그러면 영속성 컨텍스트 처음부터 만들어짐
⇒ 테스트 케이스 볼때 쿼리가 처음부터 작성되니까 확인용으로 유용함
em.close()
: 영속성 컨텍스트 닫아서 관리X, 데이터 변경 안됨