Entity Manager Factory
- 고객이 요청을 보낼 때마다 Entity Manager를 생성해준다.
- Entity Manger는 내부적으로 conn를 사용해 DB를 사용한다.
영속성 컨텍스트
- 엔티티를 영구 저장하는 환경!
- EntityManager.persist(entity)
=> 영속성 컨텍스를 통해서 entity를 영속한다
=> Entity를 영속성 컨텍스트에 저장한다
영속성 컨텍스트는 논리적인 개념이다.
Entity Manager를 통해 영속성 컨텍스트에 접근
엔티티의 생명주기
- 비영속: 영속성 컨텍스트와 전혀 관계가 없다 (new 상태)
member 객체를 생성했지만 entityManager에 넣지 않은 상태
JPA와 DB와 관련이 없
Member member = new Member("member1", "회원1");
- 영속: 영속성 컨텍스트에 관리되는 상태
객체가 영속 상태가 된다. 하지만 DB에 쿼리가 날라가진 않는다
Member member = new Member("member1", "회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member); //여기부터 영속상태가 된다
- 준영속: 영속성 컨텍스트에 저장되었다가 분리된 상태
em.detach(member);
영속성 컨텍스트에서 분리시킨다.
- 삭제: 삭제된 상태
em.remove(member);
객체를 삭제하는 상태
영속성 컨텍스트를 사용하는 이유
- 어플리케이션이랑 DB 중간에 무언가 있다.(버퍼링이 있다)
1. 1차 캐시, 엔티티 조회
EntityManager 안에 1차 캐시다 있다.
pk, 객체로 매핑되어 있다.
@Id | Entity |
"member1" | member1 |
-1차캐시-
em.find를 하면 영속 컨텍스트는 DB를 먼저 보는 것이 아닌 1차 캐시에서 먼저 조회한다.
(DB에 없어도 DB에 있는것처럼 느껴진다)
따라서 select 쿼리가 날라가지 않는다!
em.find("member2")를 하면
1차 캐시에 없다!
없으면 DB에서 member2를 찾고
찾은 값을 1차 캐시에 저장하고
member2를 반환한다.
@Id | Entity |
"member1" | member1 |
"member2" | member2 |
하지만 entityManager를 Transaction에 따라 하나씩 만들어지기 때문에 자주 사용되지는 않는다.
** 하지만
하이버네이트는 1차 캐시에 데이터 저장 시 primary key(with @Id)를 키로 사용하기 때문에 다른 컬럼을 사용하여 엔티티 조회 시 1차 캐시에서 이를 찾을 수 없다.
예를 들어 findById로 찾으면 1차 캐시에 들어가지만 findByMember로 들어간 값은 1차 캐시에 안들어가나?
테스트해보
2. 영속 엔티티의 동일성 보장
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 DB가 아닌 애플리케이션 차원에서 제공
=> 같은 트랜잭션에서는 같은 값이 나오는게 보장된다.
Member findMember1 = em.find(Member.class,101L);
Member findMember2 = em.find(Member.class,101L);
findMember1 == findMember2 //true
3. 트랜잭션을 지원하는 쓰기 지연
em.persist(memberA)
em.persist(memberB) => 여기까지는 데이터베이스에 보내지 않는다.
transaction.commit() 여기서 SQL를 보낸다.
그게 어떻게 가능하냐?
영속 컨텍스트 안에는 쓰기 지연 SQL 저장소가 있다.
처음에 persist(memberA)를 하면
1차 캐시에 넣는다.
INSERT SQL를 쓰기 지연 SQL 저장소에 등록한다.
그러면 언제 SQL query가 날라가냐
=> transaction.commit()를 하는 순간이다.
flush가 되면서 SQL문이 날라간다
DB commit이 된다.
버퍼링을 모아서 write 한다.
그러면 commit하기 전에 다른 영속 엔티티에서 조회하면 조회가 되냐?
안될거다
4. 변경 감지
dirty checking
Member member = em.find(Member.class, 150L);
member.setName("zzzz");
//em.persist(member) //할 필요 없다. 값만 바꾼걸로 update 쿼리를 날린다.
transaction.commit();
어떻게 가능하지?
JPA는 tansaction.commit을 하면 기본적으로 flush()를 먼저 한다.
스냅샷을 찍는다.
@Id | Entity | 스냅샷 |
"member1" | member1 | member1 스냅샷 |
"member2" | member2 | member2 스냅샷 |
commit될때 엔티티와 스냅샷을 다 비교한다.
바뀐 값을 쓰기 지연 SQL에 넣는다.
그 뒤 flush + commit
5. 지연 로딩
find VS JPQL
em.find()는 영속성 컨텍스트를 활용하므로 => 1차 캐시에서 찾는다, 없으면 DB에서 검색한다.
하지만 JPQL은 항상 DB에서 찾는다! 하지만 디비에서 찾은 엔티티가 영속성 컨텍스트에 포함된다면 DB에서 찾은 값을 버리고 영속성 컨텍스트에서 찾은 값으로 바꾼다.