자바 ORM 표준 JPA 프로그래밍 | 03. 영속성 관리
엔티티 매니저: 엔티티를 저장, 수정, 삭제 조회하는 일을 처리한다.
엔티티를 저장하는 가상의 데이터베이스로 생각하면 된다.
엔티티 매니저 팩토리와 엔티티 매니저
데이터베이스를 1개만 사용하는 경우 EntityManagerFactory도 1개만 생성한다.
// 공장 만들기, 비용이 많이 든다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
persistence.xml에 있는 정보를 바탕으로 DB Driver, user name, password, jdbc url을 설정해서 EntityManagerFactory를 만든다.
이후 필요할 때마다 엔티티 매니저를 생성한다.
// 공장에서 엔티티 매니저 생성 비용이 거의 안든다.
EntityManager em = emf.createEntityManager();
엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유가 가능하다.
엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 공유하면 안 된다.
엔티티 매니저는 트랜잭션을 시작할 때 커넥션을 획득한다.
영속성 컨텍스트란?
영속성 컨텍스트 = 엔티티를 영구 저장하는 환경
엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어진다.
하지만 설정에 따라 달라질 수 있다.
1 영속성 컨텍스트 = 1 엔티티 매니저
엔티티의 생명주기
- 비영속(New): 영속성 컨텍스트와 관계없는 상태
- 영속(Managed): 영속성 컨텍스트에 저장된 상태
- 준영속(Detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(Removed): 영속성 컨텍스트에서 엔티티가 삭제된 상태
영속성 컨텍스트의 특징
1 영속성 컨텍스트와 식별자 값
영속성 컨텍스트는 엔티티를 @Id로 구분한다. 반드시 있어야 한다.
2 영속성 컨텍스트와 데이터베이스 저장
영속성 컨텍스트에 엔티티를 저장하면 flush() 이후에 데이터베이스에 반영한다.
3 영속성 컨텍스트가 엔티티를 관리하는 장점
- 1차 캐시
- 동일성 보장
- 트랜잭션 쓰기 지연
- 변경 감지
- 지연 로딩
엔티티 조회
1차 캐시: 영속성 컨텍스트 내부의 캐시
영속상태의 엔티티가 저장되는 곳이다.
@Id로 엔티티를 구분한다.
1차 캐시에서 조회
em.find(Member.class, "member1");
위의 조회 코드를 호출했을 때,
1차 캐시에서 먼저 찾고, 없으면 데이터베이스에서 조회한다.
데이터베이스에서 조회하면 영속성 컨텍스트(1차 캐시)에 저장하고 저장한 엔티티 값을 반환한다.
이로 인해 다음에 똑같은 조회를 실행하게 되면 데이터베이스에서 조회하지 않고, 1차 캐시에서 바로 가져오기 때문에 성능상 이점이 있다.
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member2");
System.out.println(a == b); // 동일성 비교 -> true
영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.
- 동일성: 참조 값이 같은지
- 동등성: 인스턴스가 가지고 있는 값이 같은지 equals()로 구현해야 함
엔티티 등록
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜젝션 시작해야함
transaction.begin();
// 쓰기 지연 SQL 저장소에 SQL 저장한다.
em.persist(memberA);
em.persist(memberB);
// 커밋하는 순간 데이터베이스에 INSERT SQL 문을 보낸다.
transaction.commit();
트랜잭션을 commit() 하면 엔티티 매니저는 영속성 컨텍스트를 flush() 한다.
영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화한다.
쓰기 지연 저장소에 저장된 SQL들을 DB에서 실행한다.
이후 DB 트랜잭션을 commit()한다.
엔티티 수정
1. 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출된다.
2. 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
3. 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
4. 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
5. 데이터베이스 트랜잭션을 커밋한다.
기본적으로 Update SQL은 모든 필드를 포함한다. 하이버네이트 확장기능인 @DynamicUpdate, @DynamicInsert를 사용하면 수정한 필드만, null이 아닌 필드만 수정 및 저장 SQL을 만들 수 있다.
엔티티 삭제
엔티티를 삭제한 후 commit()을 수행하면 flush()가 동작하고, DB에 삭제 쿼리를 전달한다.
하지만, em.remove(memberA)를 호출한 후 나머지는 가비지 컬렉션 대상이 되도록 두는 것이 좋다.
영속성 컨텍스트에서는 remove() 실행 후 제거된다.
플러시
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
flush() ->
변경 감지를 통해 영속성 컨텍스트 내부의 엔티티를 스냅샷과 비교 ->
수정 쿼리 생성 ->
쓰기 지연 SQL 저장소에 등록 ->
쿼리를 DB에 전송
영속성 컨텍스트를 플러시 하는 방법
em.flush()를 직접 호출한다.
영속성 컨텍스트 강제 플러시, 테스트 코드 말고는 별로 사용 안 한다.
트랜잭션 커밋 시 플러시 자동 호출
트랜잭션 커밋 전 플러시를 호출해야 하는데 JPA는 자동으로 해준다.
JPQL 쿼리 실행 시 플러시 자동 호출
JPQL 쿼리 실행 전 영속성 컨텍스트에 있던 SQL을 먼저 DB에서 실행시키고 JPQL쿼리를 실행하기 위해 플러시를 자동으로 호출한다.
플러시 모드 옵션
- FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시, 기본값
- FlushModeType.COMMIT: 커밋할 때만 플러시
준영속
준영속 상태는 엔티티가 영속성 상태에서 분리됐기 때문에 영속성 컨텍스트가 제공하는 기능은 사용 못한다.
영속 -> 준영속 만드는 방법
em.detach(entity);
em.clear();
em.close();
detach()
특정 엔티티를 준영속 상태로 만든다.
영속성 컨텍스트에서 해당 엔티티의 정보가 제거된다.
쓰기 지연 SQL 저장소의 엔티티 관련 SQL도 제거된다.
clear()
영속성 컨텍스트를 초기화해서 모든 엔티티를 준영속 상태로 만든다.
이 이후에 member.setUserName("name");을 실행해도 1차 캐시 내부에 아무것도 없기 때문에 변경 감지가 동작하지 않고, 해당 SQL은 실행되지 않는다.
close()
영속성 컨텍스트를 종료하면 모든 엔티티가 준영속 상태로 된다.
merge()
준영속 상태의 엔티티를 merge()를 통해 다시 영속 상태로 변경할 수 있다.
새로운 영속 상태의 엔티티를 영속성 컨텍스트에 저장한다.
member는 준영속상태일 때,
Member memberMerge = em.merge(member);
를 하게 되면 member는 여전히 준영속상태고, memberMerge는 영속상태가 된다.
merge를 할 때 Member는 1차 캐시에 없으므로 DB에서 조회하게 되고, member의 값으로 덮어쓴다.
비영속 병합
영속성 컨텍스트 외부의 엔티티도 merge를 통해 영속 상태로 만들 수 있다.
DB에도 없으면 새로 생성해서 병합한다.
따라서 병합은 save or update 기능을 수행한다.
정리
- 엔티티 매니저는 엔티티 매니저 팩토리에서 생성한다.
- 영속성 컨택스트는 객체를 보관하는 가상 데이터베이스의 역할을 한다.
따라서 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경감지, 지연 로딩 기능을 사용할 수 있다.
- 영속성 컨택스트의 엔티티는 트랜잭션 commit 할 때 flush가 수행되므로 데이터베이스에 반영된다.
- 준영속 상태의 엔티티는 영속성 컨텍스트의 기능을 사용할 수 없다.
참고자료
- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"