3장_ 영속성 관리
Updated:
1. 엔티티 매니저 팩토리와 엔티티 매니저
엔티티 매니저 팩토리는 이름 그대로 엔티티 매니저를 만드는 공장인데, 공장을 만드는 비용은 상당히 크다.
따라서 한 개만 만들어서 애플리케이션 전체에서 공유하도록 설계되어 있다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
반면에 공장에서 엔티티 매니저를 생성하는 비용은 거의 들지 않는다.
EntityManager em = emf.createEntityManager();
그리고 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유해도 되지만,
엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다.
2. 영속성 컨텍스트란?
‘엔티티를 영구 저장하는 환경’ 이라는 뜻이다.
아래 코드를 정확히 이야기하면 persist() 메소드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다는 의미다.
em.persist(member);
3. 엔티티의 생명주기
엔티티에는 4가지 상태가 존재한다.
- 비영속(new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속(managed)
- 영속성 컨텍스트에 저장된 상태
- 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라고 한다
em.persist(member); // em.find() 나 JPQL도 해당
- 준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
- 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다
em.detach(member); em.close(); em.clear();
- 삭제(removed)
- 삭제된 상태
- 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다
em.remove(member);
4. 영속성 컨텍스트의 특징
- 영속성 컨텍스트와 식별자 값
- 영속성 컨텍스트는 엔티티를 식별자 값 (@Id로 테이블의 기본 키와 매핑한 값) 으로 구분한다
- 따라서 영속 상태는 식별자 값이 반드시 있어야 한다. 식별자 값이 없으면 예외가 발생한다
- 영속성 컨텍스트와 데이터베이스 저장
- 영속성 컨텍스트에 엔티티를 저장하면 트랜잭션을 커밋할 때 데이터베이스에 반영한다
- 이것을 플러시(flush) 라 한다
- 영속성 컨텍스트가 엔티티를 관리 시 장점
- 1차 캐시
- em.find() 를 호출하면 먼저 1차 캐시에서 엔티티를 찾고, 만약 찾는 엔티티가 없으면 데이터베이스에서 조회한다
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 이 기능을 잘 활용하면 모아둔 등록 쿼리를 데이터베이스에 한 번에 전달해서 성능을 최적화할 수 있다
- 변경 감지
- JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이것을 스냅샷이라 한다
- 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다
- 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다
- 데이터베이스 트랜잭션을 커밋한다
- 기본 전략은 엔티티의 모든 필드를 업데이트 한다 (변경된 부분만 수정하지 않는다)
- 이로 인해서 데이터 전송량이 증가하는 단점이 있겠지만, 수정 쿼리가 항상 같기 때문에 한 번 파싱된 쿼리를 재사용할 수 있다
- 동적으로 쿼리를 생성하는 @DynamicUpdate, @DynamicInsert 도 있다는 것 참고
- 지연 로딩
- 1차 캐시
5. 플러시
플러시(flush()) 는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
- 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다
- 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다 (등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 플러시하는 방법 3가지
- em.flush() 를 직접 호출한다
- 트랜잭션 커밋 시 플러시가 자동 호출된다
- JPQL 쿼리 실행 시 플러시가 자동 호출된다
- 참고) 식별자를 기준으로 조회하는 find() 메소드를 호출할 때는 플러시가 실행되지 않는다
엔티티 매니저에 플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType을 사용하면 된다.
- FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시 (기본값)
- FlushModeType.COMMIT: 커밋할 때만 플러시
명심할 부분은
플러시는 영속성 컨텍스트에 보관된 엔티티를 지우는 개념이 아니라
변경 내용을 데이터베이스에 동기화하는 것임을 잊지말자!
6. 준영속
준영속 상태의 특징
- 거의 비영속 상태에 가깝다
- 영속성 컨텍스트가 관리하지 않으므로 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다
- 식별자 값을 가지고 있다
- 비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 이미 한 번 영속 상태였으므로 반드시 식별자 값을 가지고 있다
- 지연 로딩을 할 수 없다
- 지연 로딩(LAZY LOADING) 은 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법이다
- 하지만 준영속 상태는 영속성 컨텍스트가 더는 관리하지 않으므로 지연 로딩 시 문제가 발생한다
병합: merge()
- 준영속 상태의 엔티티를 다시 영속 상태로 변경하는 방법
- 파라미터로 넘어온 준영속 엔티티를 사용해서 새롭게 병합된 영속 상태의 엔티티를 반환한다
- 파라미터로 넘어온 엔티티는 병합 후에도 준영속 상태로 남아 있는다
- 병합은 준영속, 비영속을 신경 쓰지 않는다
- 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합하고 조회할 수 없으면 새로 생성해서 병합한다
- 따라서 병합은 save or update 기능을 수행한다
Leave a comment