JPA를 공부하면 계속 나오는 영속성에 대해서 정리.
JPA의 엔티티 매니저(EntityManager)는 엔티티 저장, 수정, 삭제 조회 등 엔티티와 관련된 모든 일을 처리한다.
엔티티 매니저는 엔티티 매니저 팩토리를 통해 생성되는데, 팩토리는 아래와 같이 생성할 수 있다.
팩토리는 일반적으로 하나의 DB를 사용하는 경우 하나의 Factory만 생성한다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("note");
엔티티 매니저는 팩토리를 통해 필요할 때 마다 생성할 수 있다.
EntityManager em = emf.createEntityManager();
- EntityManagerFactory
- 생성 비용이 크다, 어플리케이션 전체에서 하나를 가지고 공유한다.
- 여러 스레드 동시 접근에 대한 문제 발생 하지 않음.
- EntityManager
- 팩토리를 통해 생성, 엔티티 매니저를 생성하는 비용은 거의 들지 않는다.
- 여러 스레드 동시 접근 시 동시성 문제 발생.
팩토리에서 엔티티 매니저를 생성하고, 엔티티 매니저는 트랜잭션이 시작될 때 DB 커넥션을 획득해서 DB와 관련된 일들을 처리하는 방식.
엔티티를 영구 저장하는 환경. 엔티티 매니저를 통해서 영속성 컨텍스트에 접근하거나 관리를 할 수 있다.
EntityManager em = emf.createEntityManager();
em.persist(member);
위와 같은 상황에서 persist() 메소드는 엔티티 매니저를 통해 회원 엔티티를 영속성 컨텍스트상에 저장하는데, 저장 이후부터 영속성 컨텍스트가 멤버 엔티티를 관리하게 된다.
여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수 있음. 어떤 경우인진 모르겠지만 트랜잭션 전파가 일어나는 경우에 해당하지 않을까 싶다.
영속성 컨텍스트는 아래와 같은 특징들을 가진다.
엔티티를 식별자 값(@Id로 테이블 기본키와 매핑한 값)으로 구분. 따라서 영속 상태는 반드시 식별 값이 필요하다. 없으면 예외 발생
트랜잭션을 커밋하는 순간 영속성 컨텍스트에 저장된 엔티티를 데이터베이스에 반영한다.
- 1차 캐시
영속성 컨텍스트 내부에 키 @Id, 엔티티를 값으로 가지는 Map 형태의 캐시 제공.
- 동일성 보장
1차 캐시의 엔티티 인스턴스 반환을 통해 동일성 보장 제공.)
- 트랜잭션 지원하는 쓰기 지연
트랜잭션 커밋 전까지 발생한 INSERT(persist)를 내부 쿼리 저장소에 저장한 후 커밋 시 모아둔 쿼리를 전송한다.
- 변경 감지
엔티티를 영속성 컨텍스트에 보관할 때 스냅샷 생성. 플러시 시점에 스냅샷과 엔티티를 비교해 변경된 엔티티를 찾고 수정 쿼리를 자동으로 생성해 쓰기 지연 SQL 저장소에 보낸 뒤 커밋 시 반영.(영속상태의 엔티티만 적용.)
- 지연 로딩
프록시 객체를 통한 지연 로딩 제공. 나중에 더 살펴보자.
엔티티는 아래와 같은 생명 주기를 가진다.
엔티티 객체를 생성한 경우, 객체 상태라 DB나 영속성 컨텍스트와 전혀 상관 없는 상태
Member member = new Member("안녕!");엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장한 상태
em.persist(member);영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태.
em.detach(member); //엔티티를 영속성 컨텍스트에서 분리, 준영속 상태 em.close(); //영속성 컨텍스트가 닫힌 경우엔티티를 영속성 컨텍스트, DB에서 삭제.
em.remove(member);
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것. 플러시를 실행하면 아래와 같은 일이 발생한다.
- 변경 감지가 동작, 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소 쿼리를 DB에 전송(등록, 수정, 삭제)
영속성 컨텍스트를 플러시하는 방법은 3가지 방법이 있다.
- em.flush()
- 트랜잭션 커밋
- JPQL 커리 실행 시 플러시 자동 호출.
JPQL이나 Criteria 같은 객체지향 쿼리 호출 시 플러시 실행. 아래와 같은 경우 쿼리 결과가 조회되지 않는 문제때문에 플러시를 실행한다.
em.persist(memberA); em.persist(memberB); query = em.createQuery("select m from Member m", Member.class); List<Member> members = query.getResultList();
엔티티 매니저에 직접 플러시 모드를 지정할 수 있다.
- FlushModeType.AUTO: 커밋이나 쿼리 실행 시 플러시(기본값);
- FlushModeType.COMMIT: 커밋할 때만 플러시.
별도 설정하지 않는 경우 AUTO로 동작하는데, 성능 최적화를 위해 COMMIT을 사용하는 경우도 있다고 함.
em.setFlushMode(FlushModeType.COMMIT);