[JPA] 플러시, 준영속 상태, 병합
플러시(flush())
플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영한다.
절대 보관된 엔티티를 지우는 작업이 아니라 DB에 동기화하는 동작이다.
플러시 실행시 동작
1. 변경 감지가 동작하여 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해 수정된 엔티티를 찾는다.
2. 수정된 엔티티는 수정 쿼리를 만들어 쓰지 지연 SQL 저장소에 저장한다.
3. 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송한다. (등록, 수정, 삭제 쿼리)
플러시 방법
1. em.flush()를 직접 호출
2. 트랜잭션 커밋 시 플러시 자동 호출
3. JPQL 쿼리 실행 시 플러시 자동 호출
DB에 변경 내용을 SQL로 보내지 않고 트랜잭션 커밋만하면 DB에 반영되지 않는다.
그래서 트랜잭션이 종료하기전에 JPA는 플러시를 자동으로 호출한다.
그럼 JPQL 쿼리 실행시 왜 자동 호출 할까? 어떤 문제가 있기에?? 아래의 코드를 보자.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
persist()로 영속 상태로 만들었으나 아직 DB에 반영되지는 않았다.
이때 JPQL은 SQL로 변환되어 DB를 조회한다. 그러나 아직 데이터는 없다.
그래서 JPA는 JPQL을 실행할 때 플러시를 자동 호출한다.
*find() 메소드는 플러시를 호출하지 않는다.
준영속 상태
영속 상태의 엔티티를 준영속 상태로 만드는 방법
1. em.detach(entity) : 준영속 상태로 전환
2. em.clear() : 영속성 컨텍스트 초기화
3. em.close() : 영속성 컨텍스트 종료
em.detach() - 준영속 상태로 전환
// 비영속 상태
Member member = new Member();
member.setId("memberA");
// 영속 상태
em.persist(member);
// 준영속 상태
em.detach(member);
// 트랜잭션 커밋
transaction.commit();
회원 엔티티를 persist로 영속화 시켰지만
다시 detach()로 영속성 컨텍스트와 분리 시켜 준영속 상태로 만들었다.
준영속 상태로 바뀜으로써 1차 캐시, 쓰기 지연 SQL 저장소에 해당 엔티티에 대한 정보는 모두 제거된다.
즉, 트랜잭션 커밋해도 DB에 저장되지 않는다.
em.clear() - 영속성 컨텍스트 초기화
영속성 컨텍스트를 모두 초기화하여 해당 영속성 컨텍스트에 있는 모든 엔티티를 준영속 상태로 만든다.
// 영속 상태
Member member = em.find(Member.class, "memberA");
// 영속성 컨텍스트 초기화
em.clear();
// 준영속 상태
member.setUserName("HELLO");
영속 상태의 엔티티를 가져왔으나 영속성 컨텍스트 초기화로 인해 준영속 상태 엔티티가 되었다.
이때 UserName을 변경하더라도 준영속 상태이기에 영속성 컨텍스트가 제공하는 변경 감지는 동작하지 않는다.
즉, 이름을 변경해도 DB에 반영되지 않는다.
em.close() - 영속성 컨텍스트 종료
해당 영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 모두 준영속 상태가 된다.
영속 상태
준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 merge(병합)을 사용하자.
merge() 메소드는 준영속 상태의 엔티티를 받아 그 정보로 새로운 영속 상태의 엔티티를 반환한다.
병합은 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 없으면 DB에서 조회한다.
DB에도 없다면 새로운 엔티티를 생성해서 병합한다.
준영속 병합
EntityManager em1 = emf.createEntityManager();
Member member = new Member();
member.setId("memberA");
member.setUserName("회원1");
em1.persist(member);
em1.close();
// 준영속 상태에서 이름 변경
member.setUserName("이름변경");
-----------------------------------------------
EntityManager em2 = emf.createEntityManager();
// 병합
Member mergeMember = em2.merge(member); // mergeMember의 name: "이름변경"
System.out.println( em2.contains(member) ); // false
System.out.println( em2.contains(mergeMember) ); // true
*contains(entity): 해당 엔티티를 영속성 컨텍스트가 관리하는지 확인하는 메소드
member 엔티티가 영속 상태로 변환되는 것이 아니라
mergeMember라는 새로운 영속 상태의 엔티티가 반환되어 나오는 것이다.
member와 mergeMember는 서로 다른 인스턴스이다.
비영속 병합
병합(merge)은 비영속 엔티티도 영속 상태로 만들 수 있다.
비영속 엔티티는 1차 캐시에도, DB에도 없으므로 병합 시
새로운 엔티티를 생성해서 병합한다.
Member member = new Member();
member = em.merge(member);
tx.commit();
즉, 병합은 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합하고 아니면 새로 생성해서 병합한다.
save, update 기능을 수행할 수 있다.