[JPA] 영속성 컨텍스트
영속성 컨텍스트
JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑하기(Object Relational Mapping)
- 영속성 컨텍스트
엔티티 매니저 팩토리 & 엔티티 매니저
- 엔티티 매니저 팩토리를 통해서 고객의 요청이 올 때마다 엔티티 매니저를 생성한다.
- 엔티티 매니저는 DB Connection을 사용해서 DB를 사용하게 된다.
영속성 컨텍스트
- JPA를 이해하는데 가장 중요한 용어
- “엔티티를 영구 저장하는 환경”이라는 뜻
- EntityManager.persist(entity);
persist() : DB에 저장하는 것이 아니라 영속성 컨텍스트를 통해서 엔티티를 영속화하는 것.
엔티티 매니저? 영속성 컨텍스트?
- 영속성 컨텍스트는 눈에 보이지 않는 추상적이고 논리적인 개념이다.
- 엔티티 매니저를 통해 영속성 컨텍스트에 접근한다.
엔티티의 생명주기
비영속 상태
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속성 컨텍스트에서 조회하여 생성한 객체가 아닌 모든 객체가 비영속 상태이다.
(new 연산자를 통해 생성한 객체 또한 비영속 상태이다.)
영속 상태
- 비영속 상태의 객체를 엔티티 매니저에 등록했을 때의 상태
- 영속성 컨텍스트에 관리되는 상태
- 객체를 생성한 다음 엔티티 매니저를 얻어와 persist 하면 영속성 컨텍스트에 올라가면서 영속 상태가 된다.
- 즉, persist()는 비영속 상태의 엔티티를 영속 상태로 등록하는 메서드인 것이다.
객체를 persist 하여 영속성 컨텍스트에 올린다고 해도 DB에 저장되는 것은 아니다!
// 회원 등록
public static void instMember(EntityManager em) {
Member memberA = new Member();
memberA.setId(20L);
memberA.setName("memberA");
System.out.println("==BEFORE==");
em.persist(memberA);
System.out.println("==AFTER==");
Member memberB = new Member();
memberB.setId(30L);
System.out.println("==BEFORE==");
memberB.setName("memberB");
System.out.println("==AFTER==");
em.persist(memberB);
}
- persist() 시점에 나가는 게 아니라 트랜잭션이 commit 되는 시점에 insert 쿼리가 날아간다.
- 트랜잭션이 commit될 때 DB에 저장된다.
준영속 상태
- detach : 영속성 컨텍스트에서 분리한다. 준영속 상태가 된다.
- remove : 엔티티 객체를 상태한 상태
영속성 컨텍스트의 이점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
1차 캐시
1. Id가 "member1" 인 member 객체를 em.persist(member)하여 영속성 컨텍스트에 저장한다.
2. em.find(Member.class, "member1") Id가 Id가 "member1" 인 Member 엔티티를 조회한다.
이때, persist(member)에서 Id가 "member1" 인 member인 객체를 1차 캐시에 저장하였기 때문에 JPA는 영속성 컨텍스트의 1차 캐시에서 데이터를 조회한다.
3. Id가 "member2" 인 member 객체를 조회 시 1차 캐시에 저장하지 않았기 때문에 데이터베이스에서 조회하여 데이터를 가져온다.(물론 DB에 member2라는 객체가 있어야 한다.)
엔티티 매니저는 고객의 요청이 있을 때마다 새로 생성되고 요청이 끝나면 소멸된다. 1차 캐시는 애플리케이션 전체에서 공유하는 캐시에 저장하는 것이 아니라 데이터베이스 한 트랜잭션 안에서만 효과가 있기 때문에 큰 도움은 되지 않는다.
동작 예시
public class JpaMain2 {
public static void main(String args[]) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// Id가 20L인 데이터는 이미 데이터베이스가 존재한다.
Member findMember1 = em.find(Member.class, 20L);
Member findMember2 = em.find(Member.class, 20L);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
- 로그를 보면 Id가 20L인 데이터를 두 번 조회했지만 SQL은 한 번만 전송된 것을 확인할 수 있다.
- 이는 첫 번째 인스턴스 조회 시 Id가 20L인 데이터는 영속상태가 되고 두 번째 조회 시 영속성 컨텍스트의 1차 캐시에서 데이터를 조회했기 때문이다.
- 성능적 이점보다는 컨셉이 주는 이점이 있다.
영속 엔티티의 동일성 보장
Member findMember1 = em.find(Member.class, 20L);
Member findMember2 = em.find(Member.class, 20L);
System.out.println(findMember1 == findMember2); // true
1차 캐시에서 데이터를 가져오기 때문에 결과 값은 True가 나온다.
- 1차캐시에서 데이터를 가져오기 때문에 결과 값은 True가 나온다.
- 1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.
엔티티 등록 트랜잭션을 지원하는 쓰기 지연
쓰기 지연은 persist() 메서드를 호출했을 때 바로 DB에 SQL이 전송되지 않는다. 트랜잭션 종료 시점까지 계속해서 SQL을 생성하여 쓰기 지연 SQL 저장소에 엔티티를 모아뒀다가 트랜잭션 커밋하는 순간, 한 번에 쓰기 지연 SQL 저장소의 SQL을 DB에 전송한다. 이것을 flush라 한다.
변경 감지(Dirty Checking)
public class JpaMain2 {
public static void main(String args[]) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// Id가 20L인 데이터는 이미 데이터베이스가 존재한다.
Member findMember1 = em.find(Member.class, 20L);
findMember1.setName("회원");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
- findMember1.setName() 값을 setter로 변경만 했을 뿐인데 UPDATE SQL이 전송된 것을 확인할 수 있다.
- JPA는 데이터베이스 트랜잭션을 커밋하는 시점에 flush()를 호출되면서 Entity와 스냅샷을 비교한다.
비교 후 바뀐 점이 있으면 DB에 Update SQL을 전송한다. - 스냅샷 : 영속성 컨텍스트에 들어온 최초시점을 등록해 둔 것
해당 글은 인프런의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 정리한 내용입니다.
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com