우리 말로 표현해보자면 엔티티를 영구 저장하는 환경으로 논리적인 개념에 가깝다.
엔티티매니저로 엔티티를 저장하거나 조회하면 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
기본적으로 엔티티매니저가 생성될 때 하나의 영속성 컨텍스트가 만들어지며, 여러 개의 엔티티매니저가 하나의 영속성 컨텍스트에 접근할 수도 있다.
persist 메소드를 “회원 엔티티를 저장한다”보다 정확히 얘기하면 “엔티티매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다”로 말할수있다.
엔티티의 생명주기
비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 순수한 객체인 상태
영속 (managed) : 영속성 컨텍스트에 저장된 상태, 영속성 컨텍스트에 의해 관리되는 상태
준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제 (removed) : 삭제된 상태
영속성 컨텍스트의 특징
영속성 컨텍스트 및 식별자
영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본키와 매핑한 값)으로 구분한다.
때문에 영속상태의 엔티티기 식별자 값이 없으면 예외가 발생한다.
영속성 컨텍스트와 데이터베이스 저장
엔티티를 저장하면 트랜잭션이 커밋될 때 영속성 컨텍스트의 내용이 데이터베이스에 반영된다. (flush)
1차 캐시
엔티티매니저 내부에 존재한 Map으로 em.find로 조회할 때 우선적으로 캐시를 조회하고 없으면
DB에서 조회한 후 1차캐시에 저장하여 영속상태로 만든다.
동일성 보장
em.find로 같은 식별자의 엔티티를 조회 시 1차 캐시를 거쳐 조회되므로
마치 스프링 Bean처럼 엔티티 객체는 동일한 객체임이 보장된다.
트랜잭션을 지원하는 쓰기 지연
엔티티매니저를 통해 데이터를 변경하려면 항상 트랜잭션을 취득해야한다.
엔티티매니저 내부에 SQL 저장소가 존재한다. persist메소드를 호출하면 우선 1차캐시에 저장되어 영속화되고
INSERT문이 SQL저장소에 저장된다. 그리고 현재 트랜잭션이 커밋될 때 SQL저장소에 쌓인 쿼리를 수행하여 DB에 반영한다.
트랜잭션이 커밋되면 엔티티매니저는 현재 영속성 컨텍스트를 flush하는데 이는 등록, 수정, 삭제한 엔티티를 DB에 반영하는 작업을 의미한다.
즉 SQL저장소에 쌓인 쿼리를 수행하여 DB를 변경한 후 실제 DB 트랜잭션을 커밋한다.
변경 감지
영속성 컨텍스트의 1차캐시엔 엔티티가 영속될 때 당시의 최초 상태를 저장되는데 이를 스냅샷이라 한다.
그리고 영속성 컨텍스트가 flush될때 현재 엔티티 상태와 스냅샷을 비교하여 상태가 다르면 UPDATE SQL을 생성하여 SQL저장소에 저장한다.
이 후 SQL저장소에 쿼리문이 수행될때 실제 DB에 반영된다.
기본적으로 JPA는 모든 필드를 UPDATE하는 전략을 사용하는데 이유는 다음과 같다.
모든 필드를 사용하면 수정 쿼리가 항상 같다. (같은 엔티티 기준, 바인딩되는 데이터는 다름)
애플리케이션 로딩 시점에 수정 쿼리를 미리 생성하고 값만 변경하여 사용할 수 있다.
데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 파싱된 쿼리를 재사용할 수 있다.
물론 필드가 많거나 저장되는 내용이 크면 성능 문제가 있을수 있는데, 하이버네이트의 경우 DinymicUpdate를 사용하여 수정된 필드에 대해서만 동적인 UPDATE문을 수행하도록 할 수도 있다.