JPA 영속성 컨텍스트 - slahsk/study GitHub Wiki
영속성 컨텍스트
JPA를 이해하는 데 가장 중요한 용어는 영속성 컨텍스트 이다.
엔티티의 생명주기
- 비영속성 : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속 : 영속성 컨텍스트에 저장된 상태
- 준영속 : 영속성 컨텍스트에 저장되어있다가 분리된 상태
- 삭제 : 삭제된 상태
영속성 컨텍스트 특징
- 영속 상태는 식별자 값이 반드시 있어야 한다.
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
엔티티 조회
//엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member");
member.setUsername("회원");
//엔티티 (영속)
em.persist(member);
이코드를 실행하면 1차 캐시에 회원 엔티티를 저장한다.
아직 데이터베이스에 자장되지 않았다.
Member member = em.find(Member.class, "member");
em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회한다.
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b);
em.find(Member.class, "member1") 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다.
영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.
엔티티 등록
EntiyManager em = emf.createEntiyManager();
EntiyTransaction transaction = em.getTransaction();
transaction.begin();//트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
transaction.commit();//트랜잭션 커밋
엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 차곡차곡 모아둔다.
그리고 트랜잭션을 커밋할때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연(transactional write-begind)이라 한다.
트랜잭션을 커밋하면 엔티티 매니저는 우선 컨텍스트를 플러시(영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 작업) 한다.
엔티티 수정
UPDATE MEMBER
SET
NAME = ?,
AGE = ?
WHERE id = ?
SQL을 사용하면 수정 쿼리를 직접 작성해다 한다.
이런 개발 방식의 문제점은 수정 쿼리가 많아지는 것은 물론이고 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 한다.
결국 직접적이든 간접적이든 비즈니스 로직이 SQL에 의존하게 된다.
변경 감지
EntiyManager em = emf.createEntiyManager();
EntiyTransaction transaction = em.getTransaction();
transaction.begin();//트랜잭션 시작
Mmeber memberA = em.find(Member.class, "memberA");
memberA.setUserName("hi");
memberA.setAge(10);
transaction.commit();
엔티티의 변경사항을 데이터베이스에 자동으로 반영 하는 기능을 변경 감지(dirty checking)라 한다.
JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는 이것을 스냅샷이라 한다.
그리고 플러시 시점에 스냡샷과 엔티티를 비교해서 변경된 엔티티를 찾는다.
변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
UPDATE MEMBER
SET
NAME = ?,
AGE = ?
WHERE id = ?
위 SQL이 생성될 것으로 예상할 수 있다.
하지만 JPA의 기본 전략은 엔티티의 모든 필드를 업데이트 한다.
UPDATE MEMBER
SET
NAME = ?,
AGE = ?,
GRADE = ?,
...
WHERE id = ?
단점
- 모든 필드를 사용하면 데이터베이스에 보내는 데이터 전송량이 증가
장점
- 수정쿼리가 항상 같다. 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있다.
@Enity
@Table(name="Member")
@org.hibernate.annotations.DynamicUpdate
public calss Member{
}
org.hibernate.annotations.DynamicUpdate 어노테이션을 사용하면 동적으로 SQL 동적으로 생성 된다.
INSERT SQL은 @DynamicInsert 있다.
엔티티 삭제
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
엔티티를 삭제하려면 전저 삭제 대상 엔티티를 조회해야 한다.
em.remove(memberA)호출하는 순간 memeberA는 영속성 컨텍스트에서 제거 된다. 이렇게 삭제된 엔티티는 재사용하지 말고 자연스럽게 가비지 컬렉션의 대상이 되도록 두는 것이 좋다.
트랜잭션을 커밋하면 플러시를 호출하며 실제 데이베이스에 삭제 쿼리를 전달한다.