영속성 컨텍스트란 무엇인가?
JPA를 이해하는데 가장 중요한 용어이다.
- 뜻: 엔티티를 영구 저장하는 환경
- EntityManager.persist(entity);
→ 영속성 컨텍스트를 이용해서 DB에 저장하는 것이 아니라 영속성 컨텍스트라는 곳에 저장하는 것 - 영속성 컨텍스트는 논리적인 개념으로, 눈에 보이지 않는다.
- 엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.
엔티티의 생명주기
- 비영속(new/transient): 객체를 생성한 상태(영속성 컨텍스트와 관계 x)
- 영속(managed): 객체를 저장한 상태(영속성 컨텍스트에 관리되는 상태)
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리도니 상태
- 삭제(removed): 삭제된 상태
[예시] 1차 캐시에서 조회
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 비영속
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
// 영속
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.name = " + findMember.getName());
// transaction을 commit하는 순간에 DB에 저장
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
==== BEFORE ====
==== AFTER ====
findMember.id = 101
findMember.name = HelloJPA
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
→ select 쿼리(조회 쿼리)가 나오지 않았다. 1차 캐시에서 데이터를 바로 가져왔기 때문에 DB에서 select 쿼리를 사용할 필요가 없다.
[예시] 1차 캐시, 조회 쿼리를 2번 연속으로 사용했을 때
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
→ 처음에는 1차 캐시에 값이 저장되어 있지 않기에 select문이 나감. 첫번째 조회문을 통해 1차 캐시에 값을 저장했기에 두번째 조회문은 select문이 나가지 않음.
JPA의 특징
1. 영속 엔티티의 동일성 보장
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println("result = " + (findMember1 == findMember2));
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
result = true
→ == 비교(영속 엔티티의 동일성 보장), 자바 컬렉션에서 꺼낸 것과 같음. 이게 가능한 이유는 1차 캐시가 존재하기 때문.
2. 엔티티 등록, 트랜잭션을 지원하는 쓰기 지연
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member1 = new Member(150L,"A");
Member member2 = new Member(160L,"B");
em.persist(member1);
em.persist(member2);
System.out.println("==========================");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
public class Member {
@Id
private Long id;
private String name;
public Member() {} // 기본 생성자를 작성하지 않으면 JPA에서는 오류 발생(빨간줄)
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
==========================
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
→ em.persist()를 할 때가 아닌 tx.commit()을 하는 시점에 insert 구문이 나간다.
3. hibernate의 batch_size 기능 - 버퍼링
<property name="hibernate.jdbc.batch_size" value="10"/>
사이즈만큼 모아서 데이터베이스에 한번에 네트워크로 쿼리들을 보내고 DB를 마무리한다. (버퍼링 기능, 모아서 한번에)
4. 변경 감지(Dirty Checking)
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = em.find(Member.class,150L);
member.setName("ZZZZZ");
// em.persist(member); 사용하면 안됨.ㅌ
System.out.println("==========================");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
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=?
→ 값만 바꿨는데 update 쿼리가 나감. JPA에는 변경 감지 존재
5. 플러시(flush)란?
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 플러시 하는 방법
1. em.flush() - 직접 호출
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member(200L, "member200");
em.persist(member);
em.flush();
System.out.println("==========================");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
==========================
2. 트랜잭션 커밋 - 플러시 자동 호출
3. JPQL 쿼리 실행 - 플러시 자동 호출
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();
→ flush가 안되어있으면 JPQL이 아무것도 조회되지 않음 → 이런 것을 방지하고자 기본 모드가 JPQL을 실행할 때 플러시 자동 호출
* 플러시 모드 옵션
- FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본값)
- FlushModeType.COMMIT: 커밋할 때만 플러시
* 플러시의 특징
1. 영속성 컨텍스트를 비우지 않는다.
2. 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
3. 트랜잭션이라는 작업 단위가 중요하다 → 커밋 직전에만 동기화하면 된다.
6. 준영속 상태
특징
- 영속 → 준영속
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
- 영속성 컨텍스트가 제공하는 기능 사용 불가
방법
1. em.detach(entity) - 특정 엔티티만 준영속 상태로 전환
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = em.find(Member.class, 150L);
member.setName("AAAAA");
em.detach(member);
System.out.println("==========================");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
==========================
→ detach를 하였기 때문에 데이터를 변경하였지만 update 쿼리가 나가지 않는다.
2. em.clear() - 영속성 컨텍스트를 완전히 초기화
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = em.find(Member.class, 150L);
member.setName("AAAAA");
em.clear();
Member member2 = em.find(Member.class, 150L);
System.out.println("==========================");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
==========================
→ 영속성 컨텍스트를 완전히 초기화하기 때문에 조회쿼리를 다시 작성해도 똑같은 조회구문이 나간다. testcase 같이 눈으로 보고 싶을 때 사용
'TIL' 카테고리의 다른 글
[TIL-6 / 230621] JPA 다양한 연관관계 매핑 (0) | 2023.06.21 |
---|---|
[TIL-5 / 230620] JPA 연관관계 매핑 기초, 연관관계의 주인 (0) | 2023.06.20 |
[TIL-4 / 230618] Java - 인터페이스 (0) | 2023.06.19 |
[TIL-3 / 230614] 다형성, JPA의 기본키 매핑 (0) | 2023.06.14 |
[TIL-2 / 230613] Java의 제어자, JPA 엔티티 매핑 (0) | 2023.06.13 |