[JPA]영속성 관리
영속성 관리
엔티티 팩토리
1
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
META-INF/persistence.xml 에 있는 정보를 바탕으로 엔티티 펙토리를 생성한다.
엔티티 매니저 팩토리는 이름 그대로 엔티티 메니저를 만드는 공장이다.
엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로다른 스레드 간에 공유해도 되지만, 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 생기므로 스레드간에 공유해서는 안된다.
영속성 컨텍스트란
‘엔티티를 영구 저장하는 환경’이라는 뜻이다. 엔티티 메니저로 엔티티를 조회하거나 저장하게되면 엔티티메니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
1
em.persist(member) // 영속성 컨텍스트 등록
persist() 메소드는 엔티티 메니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다.
엔티티의 생명주기
- 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속(managed): 영속성 컨텍스트에 저장된 상태
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed): 삭제된 상태
영속성 컨텍스트가 관리하는 엔티티를 영속 상태라 한다. 영속 상태라는 것은 영속성 컨텍스트에 의해 관리된다는 뜻이다.
영속성 컨텍스트의 특징
영속성 컨텍스트는 엔티티를 식별자 값(테이블의 기본 키(pk)와 매핑한 값)으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야한다.
엔티티 조회
영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라한다. 영속 상태의 엔티티는 모두 이곳에 저장된다. map 형식으로 key는 @id 가된다. 값은 엔티티 인스턴스다.
만약 em.find() 메소드를 전송받았을때 먼저 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터 베이스에서 조회하게 된다. (DB에서 조회한후 1차캐시에 저장후 결과값 반환)
영속 엔티티의 동일성 보장
1
2
Member a = em.find(Member.class, id);
Member b = em.find(Member.class, id);
반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 엔티티 인스턴스를 반환한다. 따라서 둘은 같인 인스턴스다. 따라서 영속성 컨텍스트는 성는아상 이점과 엔티티의 동일성을 보장한다.
엔티티 수정
SQL 수정 쿼리는 프로젝트가 점점 커지고 요구사항이 늘어나면서 수정 쿼리가 점점 추가된다.
쿼리가 늘어나게되면 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 한다.
변경 감지
JPA에서 엔티티를 수정하는 방법에 대해서 알아보자
JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이것을 스냅샷이라 한다.
그리고 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다. →
변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다. →
쓰지 지연 저장소의 SQL을 데이터베이스에 보낸다. →
데이터베이스 트랜잭션을 커밋한다.
변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다. JPA는 기본적으로 수정된 필드만 수정하는 것이 아니라 모든 필드를 업데이트 한다.
병합 merge()
준영속 상태의 엔티티를 다시 영속상태로 변경 하려면 병합을 사용하면된다. 이때 주의할 점은 기존의 준영속상태 엔티티를 영속상태로 만드는 것이 아니라 준영속 상태였던 엔티티의 정보를 받아서 새로운 영속상태의 엔티티를 만드는 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package jpabook.start;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class ExamMergeMain {
static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
public static void main(String[] args) {
Member member = createMember("memberA", "회원1");
member.setUsername("회원명변경"); // 준영속 상태에서 변경하는것
meargeMember(member);
}
private static void meargeMember(Member member) {
EntityManager em2 = emf.createEntityManager();
EntityTransaction tx2 = em2.getTransaction();
tx2.begin();
Member margeMember = em2.merge(member); //비영속 상태 member엔티티 정보를 가지고 새로운 member엔티티 생성
// 즉 수정된 이름으로 작성 되어있던 member엔티티 정보를 가지고 생성하게되는 것이다.
tx2.commit(); // 컷밋하기 전 jpa는 자동으로 flush()가 호출되는데
// 그때 저장해 두었던 스냅샷과 비교해서 수정된 부분이잇으면 수정쿼리를 지연 sql에 보내게된다.
//준영속 상태
System.out.println("member = " + member. getUsername() ) ;
//영속 상태
System.out.println("mergeMember = "+ margeMember.getUsername() );
System.out.println("em2 contains member = "+ em2.contains(member) ) ;
System.out.println("em2 contains mergeMember =" + em2.contains(margeMember) ) ;
em2.close() ;
//==영속성 컨텍스트2 종료 ==/
}
private static Member createMember(String id, String username) {
EntityManager em1 = emf.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
tx1.begin();
Member member = new Member();
member.setId(id);
member.setUsername(username);
em1.persist(member); //스냅샷 등록
tx1.commit();
em1.close(); // 영속성 컨텍스트1 종료,
// member 엔티티는 준영속 상태가 된다.
return member;
}
}
출력결과
1 2 3 4
member = 회원명변경 mergeMember = 회원명변경 em2 contains member = false em2 contains mergeMember =true