
"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.