⭐영속성 컨텍스트: 엔티티를 영구 저장하는 환경
지난 포스팅에서 회원을 등록할 때
EntityManger.persist(entity) 를 이용해서 멤버를 DB에 저장했었다.
그런데 이 개념은 정확히 말하자면,
엔티티를 DB에 저장한다는 것이 아니라 '엔티티 매니저를 통해 영속성 컨텍스트에서 관리되는 상태로 넘어간다'라는 개념으로 봐야한다.
영속성 컨텍스트는 논리적 개념이다.
1. 영속성 컨텍스트 상태
1) 비영속
객체를 생성만 한 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
2) 영속
객체를 저장한 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.creatEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member)
객체를 생성하고 em.persist(member) 를 하면
영속 컨텍스트(entityManager)에 member가 영속상태가 된다.
em.persist(member)만으로 insert 쿼리가 실행되는 것이 아니다.
트랜잭션을 commit하는 시점에 쿼리가 실행된다.
3) 준영속
em.detach(member);
member 엔티티를 영속성 컨텍스트에서 잠시 분리한다.
✅예시코드 (비교하기 위한 영속컨텍스트)
package hellojpa;
import jakarta.persistence.*;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//JPA는 트랜잭션이 중요하다.
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = em.find(Member.class, 150L);
member.setName("준영속테스트");
System.out.println("=========================");
tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
}
}
기존에 있던 member객체를 찾아서 setName을 하면 update쿼리가 실행되어 id가 150인 회원의 이름이 바뀐다.
em.detach(member)를 해서 영속컨텍스트에서 분리를 해보자.
✅준영속 예시코드
Member member = em.find(Member.class, 150L);
member.setName("준영속테스트(detach)");
em.detach(member); //준영속 상태
System.out.println("=========================");
tx.commit();
위 코드에서 em.detach(member) 코드만 추가해줬다.
이렇게 영속 컨텍스트에서 분리를 해준다음에 commit을 하면 update쿼리가 실행되지 않는다.
콘솔을 보면
Hibernate:
select
m1_0.id,
m1_0.name
from
member m1_0
where
m1_0.id=?
=========================
select쿼리만 실행되는 것을 알 수 있다.
4) 삭제
em.remove(member);
객체를 삭제한다. 영속성 컨텍스트에서 member객체가 없어진다.
2. 영속성 컨텍스트의 이점
영속성 컨텍스트는 애플리케이션과 데이터베이스 사이의 중간계층이다.
1) 1차 캐시(First-level Cache)
동일한 트랜잭션 내에서 같은 엔티티를 여러 번 조회할 때 데이터베이스를 다시 조회하지 않고, 메모리에 있는 1차 캐시를 사용하여 성능을 최적화한다.
✅1차 캐시 조회 예시
//객체 생성
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//영속성 컨텍스트에 저장(1차캐시)
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
영속성 컨텍스트에서 관리되는 객체를 조회할 때, DB까지 가지 않고 1차 캐시에서 조회한다.
✅DB에서 조회되는 경우(이미 DB에 member2가 있다고 가정)
Member findMember2 = em.find(Member.class, "member2");
영속성 컨텍스트에 없는 member2를 조회하면 1차 캐시에 없으니 DB를 조회한다.
DB에서 조회해오면서 1차 캐시에 member2를 담는다.
다음에 다시 member2를 조회하게 되면 1차 캐시에서 조회할 수 있다.
2) 변경 감지(Dirty Checking)
트랜잭션이 끝날 때 영속성 컨텍스트 내의 엔티티 상태를 자동으로 감지하여 변경된 부분만 데이터베이스에 반영한다.
이를 통해 효율적인 업데이트가 가능하다.
✅멤버 엔티티(id:150L, name:멤버A)가 영속성 컨텍스트에 있는 상황
package hellojpa;
import jakarta.persistence.*;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//JPA는 트랜잭션이 중요하다.
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = em.find(Member.class, 150L);
member.setName("변경감지테스트");
System.out.println("=========================");
tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
}
}
데이터를 변경하고 em.persist를 할 필요가 없다.
Hibernate:
select
m1_0.id,
m1_0.name
from
member m1_0
where
m1_0.id=?
=========================
Hibernate:
/* update
for hellojpa.Member */update member
set
name=?
where
id=?
값만 변경해줘도 update쿼리가 DB에서 실행된다.
DB를 조회해보면, 회원명이 '변경감지테스트'로 잘 변경된 것을 알 수 있다.
3) 쓰기 지연(Write-behind)
영속성 컨텍스트안에는 '쓰기지연 SQL 저장소'가 있다.
em.persist(entity)로 영속 컨텍스트에 객체를 저장하고 나면, 1차 캐시에 저장하고
INSERT SQL을 생성해서 '쓰기지연 SQL 저장소'에 쌓아둔다.
트랜잭션 커밋이 되면 그때 DB에 INSERT 쿼리를 보낸다.
✔️트랜잭션이 끝날 때까지 데이터베이스 쓰기 작업을 지연시켜 여러 쓰기 작업을 하나의 배치로 처리함으로써 성능을 개선한다.
✅예제 코드
package hellojpa;
import jakarta.persistence.*;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//JPA는 트랜잭션이 중요하다.
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member1= new Member(150L, "멤버A");
Member member2 = new Member(160L, "멤버B");
System.out.println("객체 저장 전");
em.persist(member1);
em.persist(member2);
System.out.println("객체 저장 후");
System.out.println("트랜잭션 커밋 전");
tx.commit();
System.out.println("트랜잭션 커밋 후");
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
}
}
✅실행 확인
객체 저장 전
객체 저장 후
트랜잭션 커밋 전
Hibernate:
/* insert for
hellojpa.Member */insert
into
member (name, id)
values
(?, ?)
Hibernate:
/* insert for
hellojpa.Member */insert
into
member (name, id)
values
(?, ?)
트랜잭션 커밋 후
em.persist(entity)를 하는 순간에는 쿼리 실행이 안되고 commit()이후에 insert 쿼리가 실행됨을 알 수 있다.
4)엔티티 동일성 보장(Entity Identity Guarantee)
동일한 트랜잭션 내에서는 동일한 엔티티를 조회할 때 항상 동일한 객체를 반환하여 일관성을 유지한다.
✅예제 코드
package hellojpa;
import jakarta.persistence.*;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//JPA는 트랜잭션이 중요하다.
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member findMember1 = em.find(Member.class, 100L);
Member findMember2 = em.find(Member.class, 100L);
System.out.println("result = " + (findMember1 == findMember2));
tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
}
}
findMember1과 findMember2를 같은 객체로 인식한다. (1차 캐시가 있기때문에 가능하다.)
3. 영속성 컨텍스트의 중요성
영속성 컨텍스트는 JPA의 핵심 개념이다.
영속성 컨텍스트는 애플리케이션과 데이터베이스 간의 상호작용을 관리하고 최적화하는 역할을 한다.
또한, 데이터베이스 작업을 추상화하여 개발자가 객체지향 프로그래밍의 방식으로 데이터를 처리할 수 있도록 한다.
이를 이해하고 활용함으로써 효율적이고 안정적인 데이터 처리를 구현할 수 있다.
'인프런 김영한 강의 정리 > 자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글
JPA 기본 | 기본키 매핑 | @id, @GeneratedValue (0) | 2024.06.10 |
---|---|
JPA 기본 | 필드와 컬럼 매핑 | @Column, @Enumerated, @Temporal, @Lob (0) | 2024.06.10 |
JPA 기본 | 데이터베이스 스키마 자동 생성 옵션 (0) | 2024.06.10 |
JPA 기본 | @Entity (0) | 2024.06.10 |
JPA 기본 | 회원관리 예제(회원등록, 조회, 수정, 삭제) (0) | 2024.06.08 |