[자바 ORM 표준 JPA 프로그래밍] 플러시, 준영속

hyeseungS·2022년 3월 30일
0

3.5 플러시

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.

플러시 실행 시

  1. 변경 감지 동작 -> 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교하여 수정된 엔티티를 찾음 -> 수정된 엔티티는 수정 쿼리를 만듦 -> 쓰기 지연 SQL 저장소에 등록
  2. 쓰기 지연 SQL 저장소의 쿼리 데이터베이스에 전송
    (등록, 수정, 삭제 쿼리)

영속성 컨텍스트를 플러시하는 3가지 방법

  1. em.flush() 직접 호출

    • 엔티티 매니저의 flush() 메소드를 직접 호출
    • 영속성 컨텍스트 강제 플러시
    • 테스트나 다른 프레임워크와 JPA를 함께 사용할 때 제외하고 거의 사용 X
  2. 트랜잭션 커밋 시 플러시 자동 호출

    • 트랜잭션을 커밋하기 전 플러시를 호출하여 영속성 컨텍스트의 변경 내용(SQL)을 데이터베이스에 반영해야 함
    • JPA는 트랜잭션 커밋 시 플러시 자동 호출
  3. JPQL 쿼리 실행 시 플러시 자동 호출

    • JPQL이나 Criteria 같은 객체 지향 쿼리를 호출 시에 자동 호출

    • Why?

      // 예제 3.6) JPQL 쿼리 실행 코드 예시
      em.persist(memberA);
      em.persist(memberB);
      em.persist(memberC);
      
      // 중간에 JPQL 실행
      query = em.createQuery("select m from Member m", Member.class);
      List<Member> members = query.getResultList();

      <1> em.persist() 호출
      -> 엔티티 memberA, memberB, memberC 영속상태로 만듦
      -> 이들은 영속성 컨텍스트에 있지만 데이터베이스에 반영 X
      <2> JPQL 실행
      -> JPQL은 SQL로 변환되어 데이터베이스에서 엔티티 조회
      -> but, 엔티티들은 아직 데이터베이스에 없어서 쿼리결과 조회 X

      따라서 쿼리 실행 직전, 영속성 컨텍스트를 플러시하여 변경 내용을 데이터베이스에 반영해야 함
      -> JPA는 JPQL을 실행할 때도 플러시를 자동 호출

    참고로, find() 메소드 호출 시에는 플러시 실행 X

플러시 모드 옵션

엔티티 매니저에 플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType 사용.

  • FlushModeType.AUTO : 커밋, 쿼리를 실행 시 플러시 (default)
  • FlushModeType.COMMIT : 커밋 실행 시에만 플러시 (성능 최적화 위해 사용)
em.setFlushMode(FlushModeType.COMMIT) // 플러시 모드 직접 설정

플러시는 영속성 컨텍스트에 보관된 엔티티를 지우는 것 X
플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것 O

3.6 준영속

영속 -> 준영속의 상태 변화에 대해 알아보자

  • 준영속 상태
    : 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된(detached) 것
    -> 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능 사용 X

영속 -> 준영속 상태로 만드는 3가지 방법

  1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환.
  2. em.clear() : 영속성 컨텍스트를 완전히 초기화.
  3. em.close() : 영속성 컨텍스트를 종료.

3.6.1 특정 엔티티를 준영속 상태로 전환 : detach()

// 예제 3.7) detach() 메소드 정의
public void detach(Object entity);

// 예제 3.8) detach() 테스트 코드
public void testDetached() {
	...
    // 회원 엔티티 생성, 비영속 상태
    Member member = new Member();
    member.setId("memberA");
    member.setUsername("회원A");
    
    // 회원 엔티티 영속 상태
    em.persist(member);
    
    // 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
    em.detach(member);
    
    transaction.commit(); // 트랜잭션 커밋
}

영속성 컨텍스트에서 memberA에 대한 모든 정보를 삭제함
  • 정리
    영속 상태 : 영속성 컨텍스트로부터 관리(managed)되는 상태
    준영속 상태 : 영속성 컨텍스트로부터 분리(detached)된 상태
    -> 더이상 관리하지 않음
    -> 영속성 컨택스트가 지원하는 기능 동작 X
    -> 쓰기 지연 SQL 저장소의 INSERT SQL 제거되어 데이터베이스에 저장 X

3.6.2 영속성 컨텍스트 초기화 : clear()

// 예제 3.9) 영속성 컨텍스트 초기화

// 엔티티 조회, 영속 상태
Member member = em.find(Member.class, "memberA");

em.clear(); // 영속성 컨텍스트 초기화;

// 준영속 상태
member.setUsername("changeName");

transaction.commit(); // 트랜잭션 커밋

영속성 컨텍스트에 있는 모든 것이 초기화됨
영속성 컨텍스트 제거하고 새로 만든 것과 같음
준영속 상태로 변경 감지 동작 X -> 데이터베이스 반영 X

3.6.3 영속성 컨텍스트 종료 : close()

// 예제 3.10) 영속성 컨텍스트 닫기
public void closeEntityManager() {

	EntityManagerFactory emf = 
    	Persistence.createEntityManagerFactory("jpabook");
        
    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    
    transaction.begin(); // [트랜잭션] - 시작
    
    Member memberA = em.find(Member.class, "memberA");
    Member memberB = em.find(Member.class, "memberB");
    
    transaction.commit(); // [트랜잭션] - 커밋
    
    em.close(); // 영속성 컨텍스트 닫기 (종료)

참고로, 영속 상태의 엔티티는 주로 영속성 컨텍스트가 종료되면서 준영속 상태가 됨.
개발자가 직접 준영속 상태 만드는 일 드뭄.

3.6.4 준영속 상태의 특징

준영속 상태가 된 엔티티는 어떻게 되는 걸까?

  • 거의 비영속 상태에 가깝다.
    1차캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 기능 동작 X
  • 식별자 값을 가지고 있다.
    준영속 상태는 이미 한 번 영속 상태였으므로 반드시 식별자 값 가짐
  • 지연 로딩을 할 수 없다.
    지연 로딩 : 실제 객체 대신 프록시 객체를 로딩해두고,
    해당 객체 실제 사용 시 영속성 컨텍스트를 통해 데이터 불러옴.

3.6.5 병합 : merge()

준영속 -> 영속 상태로 변경하는 방법
merge() 메소드는 준영속 상태의 엔티티를 받아 그 정보로 새로운 영속 상태의 엔티티를 반환.

// 예제 3.11) merge() 메소드 정의
public <T> T merge(T entity);
// 예제 3.12) merge() 사용 예
Member mergeMember = em.merge(member);
  • 준영속 병합
// 예제 3.13) 준영속 병합 예제
public class ExamMergeMain {
	
    static EntityManagerFactory emf = 
    	Persistence.createEntityManagerFactory("jpabook");
    
    public static void main(String args[]) {
    
    	/** 
        	1. member 엔티티는 createMember() 메소드의 영속성 컨텍스트1에서 영속상태
               -> 영속성 컨텍스트1 종료되면서 준영속 상태
               -> createMEmeber() 메소드는 준영속 상태의 member 엔티티 반환
        **/
    	Member member = createMember("memberA", "회원1");
        
        /**
        	2. 준영속 상태에서 변경
               -> but, member 엔티티를 관리하는 영속성 컨텍스트가 존재 X
               -> 수정 사항 데이터베이스에 반영 X
        **/
        member.setUsername("회원명변경");
        
        /**
        	3. 병합 사용하여 준영속 상태의 엔티티 수정
            -> 새로운 영속성 컨텍스트2 시작
            -> merge 호출
            -> member 엔티티는 영속성 컨텍스트2가 관리하는 영속 상태로 변경
            -> 트랜잭션 커밋 시 수정했던 회원명 데이터베이스에 반영
            -> 참고로, mergeMember라는 새로운 영속 상태의 엔티티가 반환되는 것.
        **/
        mergeMember(member);
	}
    
    static Member createMember(String id, String username) {
    	//==영속성 컨텍스트1 시작==//
        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 엔티티는 준영속 상태가 됨
        
        //==영속성 컨텍스트1 종료==//
        
        return member;
	}
    
    static void mergeMember(Member member) {
    	//==영속성 컨텍스트2 시작==//
        EntityManager em2 = emf.createEntityManager();
    	EntityTransaction tx2 = em2.getTransaction();
    	tx2.begin(); 
    
    	Member merMember = em2.merge(member);   
    	tx2.commit(); 
    
    	// 준영속 상태
        System.out.println("member = " + member.getUsername());
        
        // 영속 상태
        System.out.println("mergeMember = " + mergeMember.getUsername());
        
        // em.contains(entity) 
        // : 영속성 컨텍스트가 파라미터로 넘어온 엔티티를 관리하는지 확인하는 메소드
        // member(준영속) != mergeMember(영속)
        System.out.println("em2 contatins member = " +
        	em2.contatins(member));
        System.out.println("em2 contatins member = " +
        	em2.contatins(mergeMember));
    	em2.close();  
        
        //==영속성 컨텍스트2 종료==//
    
    }
}   

[출력 결과]

[merge() 동작 방식]

- merge()는 파라미터로 넘어온 준영속 엔티티를 사용하여 
  새롭게 병합된 영속 상태의 엔티티(mergeMember)를 반환함.
- 파라미터로 넘어온 엔티티(member)는 병합 후에도 준영속 상태

준영속 엔티티를 참조하던 변수를 영속 엔티티를 참조하도록 변경하는 것이 안전

// Member mergeMember = em2.merge(member)
member = em2.merge(member);
  • 비영속 병합

    비영속 -> 영속 상태

Member member = new Member();
Member newMember = em.merge(member); // 비영속 병합
tx.commit();
새로운 엔티티를 생성하여 병합

병합은 준영속, 비영속을 신경 쓰지 않는다.
병합은 save or update 기능을 수행.

3.7 정리

  • 엔티티 매니저엔티티 매니저 팩토리에서 생성
    -> 자바를 직접 다루는 J2SE 환경에서는 엔티티 매니저 만들면 그 내부에 영속성 컨텍스트도 함께 만들어짐
    (엔티티 매니저를 통해 접근 가능)
  • 영속성 컨텍스트 : 애플리케이션과 데이터베이스 사이에서 객체 보관하는 가상의 데이터베이스 역할
    -> 1차 캐시, 동일성 보장, 쓰기 지연, 변경감지, 지연 로딩 기능
  • 영속성 컨텍스트에 저장한 엔티티는 트랜잭션을 커밋할 때 영속성 컨텍스트가 플러시되어 데이터베이스에 반영됨
  • 준영속 상태의 엔티티 : 영속성 컨텍스트가 해당 엔티티를 더 이상 관리 X
    -> 영속성 컨텍스트가 제공하는 기능 사용 불가능

엔티티 매니저와 영속성 컨텍스트는 매핑한 엔티티를 실제 사용하는 동적인 부분.


출처 : 자바 ORM 표준 JPA 프로그래밍 책


자바 ORM 표준 JPA 프로그래밍 - 단방향 연관관계, 연관관계 사용

profile
Studying!!

0개의 댓글