영속성 컨텍스트란?
- JPA를 이해하기 위한 가장 중요한 용어
- EntityManager.persist(entity);
- persist 하는 순간 엔티티를 영속성 컨텍스트에 만든다. (영속화 한다)
- 엔티티 매니저를 통해서 접근한다.
- 눈에 보이지 않는 공간이 생긴다.
엔티티의 생명주기
비영속
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
// 객체를 생성한 상태로 (비영속 상태)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
영속
- 영속성 컨텍스트에 관리되는 상태
- 영속상태가 됬다고 바로 DB에 쿼리가 날라가는 것은 아니다.
- 트랜잭션 커밋하는 순간 DB에 쿼리가 날라간다.
// 객체를 생성한 상태로 (비영속 상태)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 객체를 저장한 상태(영속성 상태)
em.persist(member); // persist 하는 순간 영속성 컨텍스트에 member라는 엔티티가 올라간다.
준영속
- 영속성 컨텍스트에 저장되었다가 분리된 상태
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
삭제
- 삭제된 상태
em.remove(member);
영속성 컨텍스트의 이점
- 영속성 컨텍스트는 내부에 1차캐시라는 공간을 들고 있다.
- 영속성 컨텍스트를 1차캐시라고 이해해도 된다.
영속성 컨텍스트의 동작 정리
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// ==== 여기까지는 준영속 상태(객체 생성) ====
// 1차 캐시에 저장
em.persist(member);
/*
==== persist 하는 순간 영속성 상태가 되고,
영속성 컨텍스트가 만들어지며 1차캐시에 member객체가 저장됨 ====
*/
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
/*
==== em.find 하는 순간 JPA는 1차캐시에서 먼저 member1의 키 값을 가지고 찾는다
1차캐시에 member1 키 값과 데이터가 저장되어있으므로 DB에 들리지않고 바로 반환해준다 ====
*/
// 데이터베이스에서 조회
Member findMember2 = em.find(Member.class, "member2");
/*
==== em.find를 하여 member2 키 값을 가지고 1차캐시를 찾았지만 없을 경우
데이터베이스에 들려서 찾고 1차캐시에 새로운 member2 키 값과 데이터를 넣어주고 반환한다 ====
*/
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L); // 여기선 select문이 나가면 안됨
/*
- 실제 select 문이 한번만 나간다.
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
*/
트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
// persist를 하더라도 실제 DB에 SQL문을 보내지 않는다.
// === 영속성 컨텍스트 안에 쓰기지연 SQL 저장소에 쌓아둔다. ===
// 트랜잭션을 커밋하는 순간 데이터베이스에 쓰기지연 SQL 저장소에 쌓아두었던 SQL문을 보낸다.
transaction.commit(); // 트랜잭션 커밋
위의 그림 내부에서 동작 과정
- memberA를 저장하는 순간 1차 캐시에 저장함과 동시에 쓰기지연 SQL 저장소에 INSERT문(SQL)을 저장해둔다.
- memberB를 저장하는 순간 1차 캐시에 memberB도 저장해두고 쓰기지연 저장소에 마찬가지로 쌓아둔다.
- 데이터베이스에 날라가는 순간은 트랜잭션을 커밋하는 순간이다.
- 트랜잭션을 커밋하게 되면 쓰기지연 SQL 저장소에 쌓아둔 쿼리들이 flush 되면서 데이터베이스에 날라가게 된다.
try{
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");
em.persist(member1);
em.persist(member2);
System.out.println("=================");
tx.commit(); // 커밋하는 순간 디비에 날라간다.
/* 실행 결과
================= ---> 선을 먼저 긋고나서 쿼리가 나간다. */
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
엔티티 수정 - 변경 감지(Dirty Checking)
- JPA는 트랜잭션이 커밋하는 시점에 내부적으로 flush() 가 호출된다.
- 1차 캐시 안에서 엔티티와 스냅샷을 비교한다.
- 1차 캐시는 엔티티와 스냅샷으로 이루어져 있다.
- 스냅샷은 1차캐시에 최초로 들어온 상태를 본떠놓는 것
- 값이 변경될 경우 변경을 감지해서 트랜잭션이 커밋하는 순간 JPA가 엔티티와 스냅샷을 일일히 비교한다.
- 바뀐것이 확인되면 UPDATE 쿼리를 쓰기지연 저장소에 저장해두고 커밋하는 순간 DB에 쿼리를 날려준다.
try{
Member member = em.find(Member.class, 150L); // 데이터를 찾아온다음에
member.setName("ZZZZZ"); // 변경만 해주면 된다.
// 별도로 변경한 값을 다시 넣어줄 필요 없다. (persist를 다시 해줄 필요 없다는 뜻)
System.out.println("=================");
tx.commit(); // 커밋하는 순간 디비에 날라간다.
/* 실행 결과 */
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
=================
Hibernate:
/* update
hellojpa.Member */ update
Member
set
name=?
where
id=?
'Dev > JPA' 카테고리의 다른 글
RS) 준영속 상태 (0) | 2022.02.01 |
---|---|
RS) 플러시(flush) (0) | 2022.02.01 |
RS) JPA 어플리케이션 실습 (0) | 2022.02.01 |
RS) JPA(Java Persistence API) (0) | 2022.02.01 |
RS) SQL 중심적인 개발의 문제점 (0) | 2022.02.01 |