요약
- 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성합니다.
- 엔티티 매니저 팩토리를 생성하는 것은 비용이 많이 듭니다, 여러 스레드 동시 접근이 가능합니다.
- 엔티티 매니저를 생성하는 것은 비용이 많이 들지 않습니다, 여서 스레드가 동시 접근하면 동시성 문제가 발생합니다.
- 엔티티 매니저를 만들면 내부에 영속성 컨텍스트도 함께 만들어지며 엔티티 매니저를 통해서 접근이 가능합니다.
- 여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수도 있습니다.
- 영속성 컨텍스트는 객체를 보관하는 가상의 데이터베이스입니다.
- 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 읽기 쓰기 지연, 변경 감지, 지연 로딩 기능을 가능하게 합니다.
- 영속성 컨텍스트가 관리하는 엔티티를 영속 상태의 엔티티라고 하는데, 영속성 컨텍스트가 해당 엔티티를 더 이상 관리하지 못하면 준 영속 상태의 엔티티라고 합니다.
- 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능을 사용할 수 없습니다.
- 영속성 컨텍스트에 저장된 엔티티는 flush 시점에 DB에 반영되는데, 일반적으로 커밋될 때 flush가 일어납니다.
엔티티 매니저 팩토리, 엔티티 매니저
우선 엔티티 매니저란 JPA에서 영속성 컨텍스트를 관리하고, 엔티티와 관련된 작업을 수행하는 인터페이스 입니다.
엔티티 매니저는 엔티티 매니저 팩토리를 통해 생성됩니다.
// 비용이 높음
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
// 비용이 낮음
EntityManager em = emf.createEntityManager();
엔티티 매니저 팩토리는 엔티티 매니저를 생성하는 역할을 합니다.
엔티티 매니저 팩토리를 생성하는 것은 초기화 비용이 큽니다. 하지만 여러 스레드에서 동시에 접근할 수 있습니다.
엔티티 매니저는 엔티티 매니저 팩토리를 통해 생성됩니다.
엔티티 매니저의 생성은 초기화 비용이 크지 않지만, 여러 스레드에서 동시에 접근하면 동시성 문제가 발생할 수 있습니다.
영속성 컨텍스트란?
영속성 컨텍스트란 엔티티를 영구 저장하는 환경입니다.
논리적인 개념이기 때문에 직접 볼 수는 없습니다.
영속성 컨텍스트는 엔티티 매니저를 생성할 때 함께 생성됩니다.
엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고 관리할 수 있습니다.
여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수도 있습니다.
영속성 컨텍스트는 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 읽기 쓰기 지연, 변경 감지 (dirty checking), 지연 로딩 등의 기능을 제공합니다.
엔티티의 생명주기
엔티티는 4가지 상태를 가질 수 있습니다.
- New 비영속 상태 : 영속성 컨텍스트와 관련이 없는 상태. persist()를 하기 이전 상태.
- Managed 영속 상태 : 영속성 컨텍스트에 저장된 상태, 영속성 컨텍스트에 의해 관리되는 상태.
- Detached 준영속 상태 : 영속성 컨텍스트에 저장됐다가 분리된 상태.
- Removed 삭제 상태 : 삭제된 상태. 영속성 컨텍스트와 데이터베이스에서 삭제된 상태.

영속성 컨텍스트의 특징
영속성 컨텍스트는 식별자 (@Id로 기본키와 매핑한 값)으로 구분합니다.
영속 상태는 식별자 값이 반드시 있어야 합니다. (없으면 예외가 발생)
CRUD 작업과 영속성 컨텍스트의 동작 과정을 보겠습니다.
1. 엔티티 조회
영속성 컨텍스트는 1차 캐시라는 것을 가지고 있습니다.
(내부에 Map이 하나 있는데 키:값 = @Id:엔티티인스턴스 으로 이루어져 있습니다.)
Member 엔티티에서 식별자 Id가 member1인 Member를 조회해봅시다.
엔티티 매니저를 통해 조회를 수행할 수 있습니다.
em.find(Member.class, "member1");
조회를 하게 되면 아래와 같은 작업이 일어납니다.

- 우선 엔티티 매니저의 영속 컨텍스트의 1차 캐시를 확인하고 값이 존재하면 반환합니다.
- 만약 1차 캐시에 값이 없다면 DB를 조회 후에 1차 캐시에 값을 저장하고 반환합니다.
캐시를 통해 성능상 이점과 엔티티의 동일성을 보장합니다.
동일성 : 인스터스가 같은 것, ==
동등성 : 인스턴스가 다를 수 있으나 가진 값은 같은 것 equals()
2. 엔티티 등록
엔티티 매니저를 통해 여러 엔티티를 영속성 컨텍스트에 등록해봅시다.
em.persist(memberA);
em.persist(memberB);
// 커밋하는 시점에 Insert SQL문을 실행한다.
transaction.commit();
persist()는 비영속 상태의 엔티티를 영속성 컨텍스트로 등록해 영속화합니다.
실제 DB에 반영되는 것은 commit하는 시점입니다.
그 전까진 Insert SQL을 차곡차곡 모아두고 이것을 쓰기 지연이라고 합니다.

위 그림을 보면 persist된 member를 1차 캐시에 키:값 쌍으로 저장하고, 쓰기 지연 SQL 저장소에 INSERT SQL 쿼리문을 차곡차곡 쌓는 것을 볼 수 있습니다.
여기서 커밋을 하게되면 아래와 같은 일이 일어납니다.

- 트랜잭션을 commit하게되면 영속성 컨텍스트를 flush합니다.
- flush는 DB와 변경 내용을 동기화하는 작업을 수행합니다. (쓰기 지연 SQL 저장소의 쿼리들을 DB에 보낸다.)
- 동기화 후에 실제 DB 트랜잭션을 commit합니다.
3. 엔티티 수정
엔티티 수정은 조금 복잡합니다.
엔티티의 변경사항을 DB의 자동 반영하는데 이를 변경 감지(Dirty Checking)라고 합니다.
JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장해두고 이를 스냅샷이라고 합니다.

순서대로 알아봅시다.
- 트랜잭션을 커밋하면 엔티티 매니저 내부의 flush가 호출됩니다.
- 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾습니다. (Dirty Checking)
- 변경된 엔티티가 있다면 수정 쿼리 UPDATE를 생성해서 쓰기 지연 SQL 저장소에 보냅니다.
- 쓰기 지연 저장소의 SQL을 데이터베이스에 보냅니다.
- 트랜잭션 커밋을 완료합니다.
참고로 JPA는 기본적으로 아래의 이유로 모든 필드를 업데이트 합니다.
- 수정 쿼리가 일정하기 때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용 가능할 수 있습니다.
- DB는 동일한 쿼리를 받게 되면 파싱된 쿼리를 재사용 하게 됩니다.
- DynamicUpdate로 동적으로 생성하는 전략을 사용해도 됩니다. (보통 컬럼 30개 이상이면 적용하는데 이 상황이면 설계를 잘못했는지를 우선 확인해봅시다.)
4. 엔티티 삭제
엔티티를 삭제하기 전 조회를 해야합니다.
Memeber memberA = em.find(Member.class, "memberA");
em.remove(memberA);
등록과 비슷하게 삭제 쿼리도 쓰기 지연 SQL 저장소에 등록하고, 트랜잭션 커밋시 flush가 호출되고 삭제 쿼리가 전달됩니다.
Flush
flush는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 작업입니다. flush를 실행하면 다음과 같은 일이 일어납니다.
- 변경 감지가 동작해 영속성 컨텍스트의 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾습니다.
- 수정된 엔티티로 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록 후 DB에 전송합니다.
영속성 컨텍스트를 플러시 하는 방법은 3가지 입니다.
- em.flush
- 트랜잭션 커밋 시 자동 호출
- JPQL 쿼리 실행 시 자동 호출
참고
자바 ORM 표준 JPA 프로그래밍 | 김영한