엔티티 매니저 팩토리와 엔티티 매니저
JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑하기
- 영속성 컨텍스트
영속성 컨텍스트
JPA를 이해하는데 가장 중요한 용어이다.
"인티티를 영구 저장하는 환경"이라는 뜻을 지님.
jpa내부 동작을 알기 위해서는 영속성 컨텍스트 이해가 필요하다
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
//entityManager.close() 전에 code 작성
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member); //member 저장
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
entityManagerFactory를 통해서 고객 요청이 올때마다 EntityManager를 생성한다.
EntityManager.persist(entity)를 하면 entity를 db에 저장하는 것이 아니라 영속성 컨텍스트에 저장하는 것이다
위 코드에서 em.persist(member)는 member 엔티티를 영속성 컨텍스트에 저장하는 것.
엔티티 매니저를 통해서 영속성 컨텍스에 접근하는데 엔티티 매니저 안에 눈에 보이지 않는 어떤 공간이 생기고 그게 영속성 컨텍스트이다.
엔티티의 생명주기
영속성 컨테스트와 엔티티의 관계에서 생명주기를 알 수 있다
비영속
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
객체를 생성한 상태, jpa와 관계없는 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원");
영속
영속성 컨텍스트에 관리되는 상태
객체를 영속 컨텍스트에 저장한 상태, 영속성 컨텍스트에 의해서 객체가 관리
em.persist하면 영속 상태가 된
Member member = new Member();
member.setId("member1");
member.setUsername("회원");
EntityManager em = emf.createEntitymanager();
em.getTransaction().begin();
//객체를 저장한 상태 (영속)
em.persist(member);
준영속
영속성 컨텍스트에 저장되었다가 준리된 상태
객체를 영속성 컨텍스트에서 분리, 영속성 컨텍스트에서 객체 삭제
em.detach(member);
삭제
삭제된 상태
객체를 삭제한 상태, db에서 객체 삭제
em.remove(member);
영속성 컨텍스트 장점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
엔티티 조회, 1차 캐시
영속성 컨텍스트 안에 1차 캐시가 있어서 엔티티를 영속시키면 1차 캐시가 생기는데 여기서 key value 테이블 형태로 생성된다.
key: 우리가 등록한 pk
value: 엔티티 자체
Member member = new Member();
member.setId("member1");
member.setUsername("회원");
EntityManager em = emf.createEntitymanager();
em.getTransaction().begin();
//객체를 저장한 상태 (영속)
em.persist(member);
엔티티 매니저로 조회하면 1차 캐시에서 조회한다.
//엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
코드 결과를 보면 조회했는데 select 쿼리가 날라가지 않았다.
1차캐시에 데이터가 없어서 데이터베이스에서 조회하는 경우
첫번째 em.find해서 101L인 회원을 찾는데 1차 캐시에 존재하지 않아서 select쿼리로 db에서 찾아온 후 1차 캐시에 저장
영속 엔티티의 동일성 보장
member a = em.find(Member.class, "member1");
member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수분을 데이터베이스가 아닌 애플리케이션 차원에서 제공
엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변겨시 트랜잭션을 시작해야한다.
transactoin.begin();
em.persist(memberA);
em.persist(memberB);
//여기까지 insert SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 insert SQL 을 보낸다
transction.commit();
em.persist해서 1차 캐시에 저장하고 쓰기 지연 SQL 저장소에 insert쿼리를 쌓아두었다가 transaction.commit()하는 시점에 쓰기 지연 SQL이 db에 날라간다.
코드 결과를 보면 =========== 이 선이 먼저 출력되고 이후에 쿼리가 나가는 걸 확인할 수 있다.
tx.commit()이 된 이후에 쓰기 지연 SQL저장소에 있는 쿼리들이 db에 날라가는 걸 확인
설정 정보에서 쓰기 지연 SQL 저장소가 얼마만큼의 쿼리를 저장할 수 있는지 설정가능
엔티티 수정 변경 감지
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit();
member.setName()하고 em.persist(member)해서 따고 영속시키지 않아도 된다.
jpa는 데이터베이스 트랜잭션 commit하는 시점에 내부적으로 flush라는게 호출된다. (flush 설명 글)
그리고 엔티티와 스냅샷을 비교한다. (스냅샷은 내가 값을 읽어온 최초 시점의 상태를 safe해둔 것.)
그리고 jpa가 엔티티와 스냅샷을 비교해서 바뀐 데이터에 대해서만 update쿼리를 생성해서 쓰기 지연 SQL 저장소에 넣어둔다.
엔티티 삭제
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제
커밋 시점에 em.remove 관련 쿼리 나간다
준영속 상태
영속인 상태의 엔티티가 영속성 컨텍스트에서 분리
영속성 컨텍스트가 제공하는 기능을 사용 못함
준영속 상태로 만드는 방법
em.detach(entity): 특정 엔티티만 준영속 상태로 전환
em.clear() : 영속성 컨텍스트를 완전히 초기화
em.close() : 영속성 컨텍스트를 종료
detach()하는 경우
select 쿼리만 날라가고 setName에 대한 update 쿼리가 날라가지 않았다.
clear()하는 경우
clear하고 다시 em.find()해서 같은 id를 조회하면 다시 db에 접근해서 데이터를 가져오는 걸 볼 수 있다(select쿼리가 두번 나감)