"Entity를 영구 저장하는 환경"이라는 뜻
영속성 컨텍스트는 논리적인 개념이라 눈에 보이지 않는다.
EntityManager를 통해 영속성 컨텍스트에 접근한다.
코드 : EntityManager.persist(entity)
- 비영속 (new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속 (managed)
영속성 컨텍스트에 관리되는 상태
- 준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed)
삭제된 상태
최초에 객체를 생성한 상태
-> 영속성 컨텍스트와 전혀 관계 없는 새로운 상태
객체 생성 후 EntityManager에 아무 요청도 하지 않은 상태
em.persist(member)
코드를 통해 영속성 컨텍스트에 객체를 추가한다.em.persist(member)
코드를 작성한다고 해서 Query가 작성되지 않는다.
tx.commit()
코드가 실행되어야 Query가 작성되어 DB에 객체가 저장된다.
public class JpaMain {
public static void main(String[] args) {
...
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
System.out.println("=== before ===");
entityManager.persist(member);
System.out.println("=== after ===");
// persist()로 1차 캐시에 이미 있기 때문에 select 쿼리가 나가지 않는다.
Member findMember = entityManager.find(Member.class, 101L);
System.out.println("findMember.id: " + findMember.getId());
System.out.println("findMember.name: " + findMember.getName());
tx.commit();
}
}
=== before ===
=== after ===
findMember.id: 101
findMember.name: HelloJPA
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
em.persist(member)
또는 em.find(Member.class, 101L)
코드 실행 시 별도의 쿼리가 나가지 않는 것을 확인할 수 있다.
tx.commit()
코드 실행 시 insert 쿼리가 나가는 것을 확인할 수 있다.
-> 이후 다시 member2를 조회할 때는, 1차 캐시에서 조회 후 바로 반환
public class JpaMain {
public static void main(String[] args) {
...
Member findMember1 = entityManager.find(Member.class, 101L);
// 같은 데이터를 조회하면 1차 캐시에서 가져오기 때문에 쿼리가 나가지 않는다.
Member findMember2 = entityManager.find(Member.class, 101L);
tx.commit();
}
}
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
1차 캐시에 저장되어있지 않은 객체 조회 시 "select from where" 쿼리가 생성되어 DB에서 해당 데이터를 조회하는 것을 확인할 수 있다.
이후 같은 객체를 다시 조회할 경우, 해당 객체가 1차 캐시에 저장되어있기 때문에 별도의 쿼리가 생성되지 않는 것을 확인할 수 있다.
특징
자바 컬렉션에서 꺼낸 데이터는 레퍼런스가 같은 특성을 가진다.
JPA도 이와 같은 동일성을 보장해준다.
1차 캐시는 Map으로 Entity 인스턴스를 캐싱하고있기 때문에, 같은 식별자(@Id 값)에 대해 매번 같은 인스턴스에 접근하게 되므로 동일성이 보장이 된다.
em.persist()
객체를 영속상태로 전환할 때 Query를 보내지 않는다.
commit()
하는 순간 DB에 Insert Query를 보낸다.
객체를 persist()
를 통해 영속 상태로 만들면 쓰기 지연 SQL저장소
에 insert 쿼리를 쌓아둔다.
이후 트랜잭션을 commit()
하는 시점에서 쓰기 지연 SQL저장소
에 저장된 쿼리가 flush()
되어 DB에 날아간다.
Member.java
@Entity
public class Member {
@Id
private Long id;
private String name;
// JPA는 내부적으로 reflection을 이용해 동적으로 객체를 생성해내기 때문에 기본 생성자가 필요하다.
// JPA 기본 스펙
public Member() {
}
// 객체 생성을 쉽게 하기 위해 새로운 생성자를 만든다.
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
...
}
JpaMain.java
public class JpaMain {
public static void main(String[] args) {
...
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");
// 쓰기 지연 SQL 저장소에 Query가 저장된다.
entityManager.persist(member1);
entityManager.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
(?, ?)
member1, member2를 저장하는 쿼리가 commit()
시점에 한번에 날아가는 것을 확인할 수 있다.
참고 : batch_size 만큼 Query를 저장해 DB에 날릴 수 있는 프로퍼티
<property name="hibernate.jdbc.batch_size" value="10"/>
버퍼와 유사하게 여러 개의 Query를 모아서 보낸다면, 성능 최적화가 가능하다.
JPA는 자바 컬렉션에 넣은 것처럼 값을 다루는 게 목적이다.
-> 컬렉션에서 꺼낸 값을 변경했을 때 다시 컬렉션에 넣는 코드를 작성하지 않는다.
마찬가지로 JPA는 데이터 변경 후에 persist()
를 하지 않아도 된다.
1차 캐시에는 @id와 @entity 외에도 스냅샷이 있다.
(최초로 영속성 컨텍스트에 들어온 상태를 저장하는 것)
commit()
시점에 Query가 flush()
되면서 Entity와 스냅샷을 비교한다.
만약 변경사항이 있으면 update Query 생성 후, DB에 반영한다.
삭제는 remove()
만 해주면 된다.
쓰기 지연 저장소에 쿼리를 모았다가 커밋 시점에 실행한다.
참고 :
김영한. 『자바 ORM 표준 JPA 프로그래밍』. 에이콘, 2015.