영속성 컨텍스트(persistence context)
영속성 컨텍스트란 엔티티를 영구 저장하는 환경이라는 의미입니다.
엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 저장합니다.
영속성 컨텍스트는 엔티티 매니저를 생성할때 만들어지면 엔티티 매니저를 통해 영속성 컨텍스트에 접근, 관리 할수 있습니다.
엔티티의 생명주기 (Life Cycle of Entity)
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속(managed) : 영속성 컨텍스트에 저장된 상태
- 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed) : 삭제된 상태
비영속
엔티티 객체를 생성한 상태로 순수한 객체 상태이므로 영속성 컨텍스트나 데이터베이스와는 관련이 없는 상태입니다.
Member member = new Member();
member.setId(1L);
member.setName("pandamun");
영속
엔티티 매니저를 통해서 엔티티를 연속성 컨텍스트에 저장한 상태, 영속성 컨텍스트가 관리하는 엔티티, 이러한 상태를 영속 상태라고 합니다.
Member member = new Member();
member.setId(1L);
member.setName("pandamun"); // 비영속 상태
entitymanager.persist(member); // 영속 상태
준영속
영속성 컨텍스트가 관리하던 엔티티(영속상태 엔티티)가 영속성 컨텍스트에서 떨어져나간상태, 관리하지 않는 상태를 말합니다.
entitymanager.detach(member);
member 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
entitymanager.close();
entitymanager을 닫아도 준영속 상태가 됩니다.
entitymanager.clear()
영속성 컨텍스트를 초기화 하면 영속성 컨텍스트가 관리하던 엔티티는 준영속 상태가 됩니다.
삭제
엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제합니다.
entitymanager.remove(member);
영속성 컨텍스트의 특징
- 영속성 컨텍스트와 식별자 값
- 영속성 컨텍스트는 엔티티를 식별자값(@Id 어노테이션을 사용하여 테이블의 pk와 매핑한 값)으로 구분합니다. 따라서 영속 상태는 식별자값이 반드시 있어야합니다. 없다면 예외가 발생합니다.
- 영속성 컨텍스트와 데이터베이스 저장
- JPA는 트랜잭션이 커밋하는 순간 영속성 컨텍스트에 저장된 엔티티를 데이터베이스에 반영합니다.(flush)
- 영속성 컨텍스트의 이점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
1차 캐시
1차 캐시는 영속성 컨텍스트 내부에 존재하는 캐시입니다.
persist() 메소드를 사용하여 영속상태로 들어간 엔티티는 모두 이곳에 저장됩니다.
@Id 어노테이션으로 메핑한 식별자 값과 같이 저장되어 조회시 1차 확인 됩니다.
조회시 1차 캐시에서 식별자 값으로 엔티티를 찾고 만약 1차 캐시에 엔티티 가 존재하지 않다면 데이터베이스를 조회하여 엔티티를 생성하여 1차 캐시에 저장하고 영속상태의 엔티티를 반환합니다.
동일성 보장
Member a = entitymanager.find(Member.class,1);
Member b = entitymanager.find(Member.class,1);
위코드와 같이 엔티티를 조회 하였을떄 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환합니다.
따라서 둘은 같은 인스턴스이므로 a == b 는 참이라는 결과가 나오게 됩니다.
영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장합니다.
- 동일성, 동등성
- 동일성(identity) : 실제 인스턴스가 같아 참조값을 비교하는 == 비교의 값이 같습니다.
- 동등성(equality) : 실제 인스턴스는 다를수 있지만 인스턴스가 가지고 있는 값이 같습니다. 자바에서의 동등성 비교는 equal() 메소드를 사용합니다.
트랜잭션을 지원하는 쓰기 지연
엔티티 매니저를 사용하여 엔티티를 등록할때 트랜잭션을 커밋하기 전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 SQL쿼리을 차곡차곡 모아둡니다. 그리고 트랜잭션을 커밋할때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연(transaction write-behind)라고 합니다.
순서대로 정렬하면 persist()메소드를 통해 영속화 시킨 엔티티를 1차캐시에 저장함과 동시에 엔티티 정보로 쿼리를 만들어 쓰기 지연 SQL 저장소에 보관합니다. 그후 커밋이 들어오게되면 영속성 컨텍스트를 flush(쓰기 지연 SQL저장소에 모인 쿼리를 데이터베이스에 보내 동기화 시키는 작업)하고 동기화가 끝나면 실제 데이터베이스 트랜젝션을 커밋합니다.
변경감지
엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경감지라고 합니다.
JPA는 엔티티를 영속성 컨텍스트에 보관할때 스냅샷(최초 상태를 복사해 저장)을 저장해 둡니다.
그때 트랜잭션이 커밋하면 엔티티 내부에 flush()가 호출되면 엔티티와 스냅샷을 비교하여 변경된 엔티티를 찾습니다. 변경된 엔티티가 있으면 수정 쿼리를 생성한후 쓰기 지연 SQL 저장소에 저장한후 flush작업을 하고 동기화가 끝나면 데이터베이스 트랜잭션을 커밋합니다.
변경 감지 기능은 영속성 컨텍스트가 관리하는 영속상태의 엔티티에만 적용됩니다.
또한 변경감지로 인해 실행된 SQL쿼리는 변경한 값만 사용하여 쿼리가 생성되는것이 아닌 엔티티의 모든 필드를 업데이트합니다.
이렇게 하면 데이터베이스에 보내는 데이터 전송량이 증가하는 단점이 있지만 아래와 같은 이점이 있습니다.
- 모든 필드를 사용하면 수정쿼리가 항상 같아 애플리캐이션 로딩시점에 수정쿼리를 미리 생성해두고 재사용 가능합니다.
- 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱한 쿼리를 재사용할수 있습니다.
@Entity
@org.hibernate.annotations.DynamicUpdate
@Table(name = "Member")
public class Member{
....
}
만약 데이터가 크거나 필드가 많다면 수정한 데이터만 사용하여 동적으로 SQL 쿼리를 생성하는 방법을 하이버네이트 확장 기능을 통해 사용할수 있습니다.