[JPA] 영속성 컨텍스트란
영속성 컨텍스트 (Persistence Context) 란 엔티티를 영구 저장하는 환경 으로, 애플리케이션과 데이터 베이스 사이에서 객체를 보관하는 논리적 개념이다.
__EntityManager__를 통해서 영속성 컨텍스트에 접근한다. (EntityManger가 생성됨녀 논리적 개념인 영속성 컨텍스가 1:1로 생성된다.)
1. EntityManagerFactory & EntityManager 복습
EntityManagerFactory는 여러 스레드에서 동시에 접근해도 안전하지만, 생성하는 비용이 큼. 따라서, EntityManagerFacotry에서는 요청이 올떄마다 생성비용이 거이 없는 EntityManager를 생성한다. 하지만, EntityManager는 Thread Not Safe 하기 때문에 여러 스레드가 동시에 접근하면 동시성 문제가 발생하기 떄문에, 요청(스레드) 별로 한개씩 할당이 되어야 한다.
정리
- EntityManagerFactory는 고객의 요청이 올 때마다 (thread가 하나 생성될 떄마다) EntityManager를 생성한다.
- EntityManager는 내부적으로 DB connection pool을 사용하여 DB에 접근한다.
- EntityManagerFactory
- JPA는 EntityManagerFactory를 만들어야 한다.
- apllication loading 시점에 딱한번 생성
- entityManagerFactory.close();
- WAS 가 종료되는 시점에 EntityManagerFactory를 닫는다.
- 그래야 내부적으로 Connection pooling에 대한 Resource가 Release 된다.
- EntityFactory
- 실제 Transaction 단위를 수행할 때마다 생성된다. 즉, 고객의 요청이 올때마다 사용했다가 닫는다.
- thread 간에 공유하면 안된다.
- entityManager.close();
- Transaction 수행후에는 반드시 EntityManager를 닫는다.
- 그래야 내부적으로 DB Connection을 반환한다.
EntityTransaction
- EntityTransaction
- Data를 “변경” 하는 모든 작업은 반드시 Transaction 안에서 이루어져야 한다.
- EntityTransaction tx = entityManager.getTransaction();
- tx.begin(); : Transaction 시작
- tx.commit() : Transaction 수행
- tx.rollback() : 작업에 문제가 생겼을시 롤백
2. @PersistenceContext
@PersistenceContext 로 EntityManager 를 선언하면, SpringContainer 가 Thread-Safe 한 영속성 컨텍스트를 가진 EntityManager 를 주입하게 된다. @PersistenceContext 로 지정된 Property에 EntityManagerFactory에서 새로운 EntityManager에서 EntityManager를 생성하거나, Transaction에 의해 기존에 생성된 EntityManager를 반환시켜 사용하게 해준다. 최신 스프링부트에서는 @Autowired로도 할 수 있다고 한다.
@PersistenceContext
private EntityManager em;
PersistenceContext Type 은 두가지가 있는데,
- Transaction-scoped persistence context
- Extended-scoped persistence context : 여러 스레드가 공유하는 영속성 컨텍스트 (extended-scoped persistence context) 를 만들 수 있다.
3. 엔티티의 생명 주기
3.1 비영속
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
Member member = new Member("sw");
3-2. 영속
영속성 컨텍스트에서 관리되는 상태
Member member = new Member("sw");
EntityManager em = entityManagerFactory.createEntityManager();
em.persist(member);
em.persist 를 한다고 DB에 바로 저장되는것이 아닌, 커밋또는 flush 이후에 DB에 저장이 된다. 그전까지는 영속성 컨텍스트에만 존재를 하게 된다.
3-3. 준영속
영속성 컨텍스트에 저장되었다가 분리된 상태
Member member = new Member("sw");
em.persist(member);
em.detach(member); // 준영속 상태, 사실 준영속상태를 만들 필요가 있나?
3-4. 삭제
em.remove(member);
실제 DB에서 삭제된 상태
4. 영속성 컨텍스트의 식별자 값
영속성 컨텍스트는 엔티티를 식별자 값(@ID로 테이블의 기본 키와 매핑한 값)으로 구분한다. 따라서 영속 상태의 엔티티는 반드시 식별자 값이 존재한다.
5. 영속성 컨텍스트 이점
5-1. 1차 캐시
em.persist(member); // 영속성 컨텍스트의 1차 캐시에 저장
Member findMember = em.find(Member.class, 1L); // 1차 캐시에서 엔티티 조회
1차 캐시에 엔티티가 존재 하지 않을 경우 DB에서 직접 조회하고 1차 캐시에 저장한후 반환을 한다. 요청이 시작되면 영속성 컨텍스트를 생성하고, 끝나면 영속성 컨텍스트를 제거한다. 이때, 1차 캐시 또한 삭제가 된다. 따라서, 어플리케이션 전체에서 1차 캐시를 공유하는것은 아니다. (어플리케이션 전체에서 공유하는 캐시는 2차 캐시)
5-2. 동일성 보장
이전장 설명
5-3. 트랜잭션을 지원하는 쓰기지연
5-4. 변경감지 (Dirty Read)
엔티티 매니저는 트랜잭션을 커밋하거나, 명시적으로 엔티티 매니저의 flush() 메서드를 호출하게 되면, 엔티티의 변경 사항을 데이터 베이스에 반영한다. (변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에게만 해당한다. ) 트랜잭션을 커밋하기 직전에 엔티티 매니저는 우선 영속성 컨텍스트를 플러시한다. 플러시는 영속성 컨텍스트의 변경 내용을 데이트 베이스에 동기화 하는 작업인데 이 때 등록, 수정, 삭제한 엔티티를 데이트베이스에 반영한다,
5-5. 병합(Merge)
병합은 준영속 상태의 엔티티를 영속 상태로 변경 할 때 사용 가능하다.
- em.merge(param) 를 호출
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
- 만약 1차 캐시에 엔티티가 없다면 데이터베이스에서 엔티티를 조회, 1차 캐시에 저장
- 조회한 영속 엔티티에 파라미터로 넘어온 객체의 값을 모두 채워넣는다.
- 영속 상태인 객체를 반환한다.
변경 감지기능을 사용하면 __원하는 속성__만 변경할 수 있지만, 병합을 사용하면 __모든 속성__이 변경된다. 또한 병합시 객체에 값이 없는 속성이 있으면 null로 업데이트를 한다.
6. 플러시
플러시란 영속성 컨텍스트의 변경 내용을 데이터 베이스에 반영을 하는것
6.1. 플러시가 발생했을 때
- 변경 감지
- 변경된 엔티티 쓰기 지연 SQL 저장소에 UPDATE 쿼리 저장
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
6.2. 영속성 컨텍스트를 플러시 하는 방법
- em.flush()
- 트랜잭션 커밋
- JPQL 쿼리 실행
- JPQL 수행전 자동으로 flush를 발생시켜 DB와 영속성 컨텍스트간의 동기화를 수행한다.
6.3. 플러시 유의 사항
- 영속성 컨텍스트를 비우지 않는다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화한다.
참고
https://gmlwjd9405.github.io/2019/08/06/persistence-context.html