소멸되지 않고 저장될 수 있는 데이터의 성질
Entity를 영구 저장하는 환경
EntityManager로 Entity를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 Entity를 보관하고 관리
persist(entity) 는 단순히 Entity를 저장하는 것이 아닌 EntityManager를 사용해서 Entity를 영속성컨텍스트에 저장
영속성 컨텍스트는 EntityManager를 생성할 때 하나 만들어짐
EntityManager를 통해서 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트 관리 가능
비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
영속(managed) : 영속성 컨텍스트에 저장된 상태
준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed) : 삭제된 상태
영속성 컨텍스트는 반드시 식별자 값(@Id로 기본키와 매핑한 값)으로 구분 => 반드시 식별자값 있어야함
영속성 컨텍스트에서는 트랜잭션을 commit 하는 순간 영속성 컨텍스트에 새로 저장된 Entity를 데이터베이스에 반영, flush() 라고 함
장점
1차 캐시 (조회)
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있음
영속 상태의 Entity는 모두 이곳에 저자
Map<@Id, Entity's Instance>
key는 식별자 값이며, 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스의 기본 키
find()를 호출했을 때, 먼저 1차 캐시에 데이터가 있는지 확인하고 없으면 데이터베이스에서 조회
동일성 보장
식별자가 같은 Entity Instance를 조회해서 다른 객체에 저장하고 동일성 비교를 하면 True
처음에 find() 해서 데이터를 가져왔을 때, 1차 캐시에 저장
두번째로 find() 해서 동일한 식별자의 데이터를 가져왔을 때는 1차 캐시에서 같은 Entity Instance를 반환하기 때문에 같음
성능상의 이점과 동일성 보장
트랜잭션을 지원하는 쓰기 지연(등록)
EntityManager는 Transaction을 커밋하기 직전까지 데이터베이스에 Entity를 저장하지 않고 내부 쿼리 저장소에 쿼리를 모아둠
Transaction을 커밋할 때, 모아둔 쿼리를 데이터베이스에 보냄 => 쓰기 지연
데이터를 저장하는 즉시 등록 쿼리를 데이터베이스에 보내는 방법이 있지만, 최종적으로 트랜잭션을 커밋해여 변경사항이 저장된다.
쓰기 지연을 통해서 쿼리를 모아두고, 한번에 보냄으로 써 성능 최적화가 가능하다.
변경 감지(수정)
조회는 find, 저장은 persist를 사용했지만, 수정엔 update가 없음
JPA는 Entity의 변경사항을 자동으로 반영하는 변경 감지 기능이 있음
Entity를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해 두는데, 이것을 Snapshot이라고 함
그리고 flush() 시점에 Snapshot과 Entity를 비교해서 변경된 Entity를 찾음
변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 Entity에만 적용
그 외 상태의 Entity는 변경되어도 데이터베이스에 반영되지 않음
변경된 Entity의 필드가 아닌 모든 필드를 update하는 쿼리 생성
이로 얻을 수 있는 이점으로는 모든 필드를 사용함으로 써, 수정 쿼리가 항상 같기 때문에 어플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용 가능
그리고 데이터베이스에 동일한 쿼리를 보냄으로 써, 이전에 한번 파싱한 쿼리를 재사용 가능
컬럼이 대략 30개 이상이 되면 DynamicUpdate를 사용해서 변경된 필드만 update되게 할 수 있음
하지면, 30개 이상의 컬럼이 존재한다는 것은 Table 설계에 책임이 제대로 분리되지 않았을 가능성 높음
지연 로딩
데이터를 조회할 때, 필요한 컬럼만을 조회함으로 써, 성능의 이점 가져옴
즉, 데이터가 필요한 시점에, 필요한 데이터 만을 가져오는 것을 의미
Entity 클래스의 필드에 fetch= FetchType.LAZY 를 부여함으로 서, 해당 필드는 지연로딩 하도록 설정 가능
실제로 해당 필드를 get 할때, 데이터를 조회
자주 사용하는 필드의 경우, Lazy를 하면 매번 쿼리가 두번씩 나감으로 써 성능상 손해
즉시로딩 FetchType.EAGER을 사용
영속성 컨텍스트를 데이터베이스에 반영 하는 것
동작 시기
flush() 직접 호출
EntityManager의 flush()를 직접 호출해서 강제로 flush
테스트 도는 JPA를 함께 사용할 때를 제외하고 거의 사용 X
Transaction.commit()시 자동 호출
변경내용을 SQL로 전달하지 않고 Transaction만 commit 하게되면 어떤 데이터도 반영 X
Transaction Commit 전에 꼭 Flush 호출해서 변경 내용 반영
JPA 에서는 commit 만 수행할 경우를 에방하기 위해서 commit할 때, flush를 자동 호출
JPQL 쿼리 실행 시 자동 호출
데이터베이스에 넘어가지 않고 있는 영속된 Entity 에 대한 정보는 JPQL 같은 객체지향 쿼리를 사용해서 데이터베이스에서 Entity를 조회하게되면 영속된 Entity는 데이터베이스에 내용이 반영되있지 않기 때문에 쿼리 결과로 조회 X
이를 방지하기 위해서 JPQL 쿼리 실행시, 영속된 Entity를 flush 해서 변경 내용을 데이터베이스에 반영하고 반영한 내용까지 쿼리결과에 포함시킨다.
식별자를 기준으로 조회하는 find() 메소드는 플러시 실행 X
특정 Entity를 준영속 상태로 전환
호출하는 순간 해당 Entity는 1차 캐시부터 쓰기 지연 SQL 저장소까지 관련된 정보가 모두 제거
해당하는 영속성 컨텍스트의 모든 Entity를 준영속 상태로 만드는 것
준영속 상태에서는 아무리 Entity의 필드를 변경해도 데이터베이스에 반영되지 않음
해당하는 영속성 컨텍스트를 종료하고, 영속 상태의 Entity를 모두 준영속 상태로 만드는 것
대부분 영속성 컨텍스트가 종료되어 Entity가 준영속 상태로 변함
개발자가 직접 준영속 상태로 만드는 일은 거의 없음
준영속 상태의 Entity를 다시 영속 상태로 변경하기 위해 사용
준영속 상태의 Entity를 받아서 새로운 영속 상태의 Entity를 반환
이게 무슨말이나면, 영속 상태의 Entity를 준영속 상태로 변경하였다고 가정해보자.
준영속 상태의 Entity의 정보를 변경하고, 다시 영속상태로 바꾼다면?
find() 할때처럼 동일한 식별자가 존재하는지 1차 캐시에서 확인 후 존재한다면 해당 데이터를, 존재하지 않는다면 데이터베이스를 조회해서 데이터를 가져오고 말그대로 merge(병합) 과정을 통해서 새로운 영속상태의 Entity를 만든다. 그렇기 때문에 준영속 상태로 돌아갔던 Entity와 새롭게 생성된 newEntity는 EntityManager에 contains를 사용해서 확인해보면 false,true가 나오며 기존의 Entity는 아직도 준영속 상태이고 새로 생성한 newEntity는 영속된 상태이다. 결국 서로 다른 Instance이기 때문에 기존의 Entity는 사용할 필요가 없다. 사용할 때는 기존의 Instance에 덮어쓰기(?) 해서 사용하는 것이 좋다.
비영속 상태의 Entity 또한 merge()가 가능하다.