JPA 영속성 컨텍스트(Persistance Context)

영속성 컨텍스트는 인스턴스로 존재하는 엔티티를 관리하고 DB에 접근하는 논리적인 영역을 의미한다.

여기서 주의해야할 점은, DB에 내용이 바로 저장되는 게 아니라 저장될 준비를 하는 곳이다!

 

영속성 컨텍스트는 엔티티를 관리하고 필요에 따라 DB의 데이터를 저장, 조회, 수정, 삭제할 수 있다. 이를 담당하는 객체 EntityManager

 

엔티티 매니저(Entity Manager)

엔티티 매니저는 영속성 컨택스트에서 엔티티를 관리한다.

JPA에서 제공하는 인터페이스로 스프링 빈으로 등록되어 있다.

엔티티 캐시를 가지고 있다.

 

영속성 컨텍스트의 영역

 

1차 캐시 저장소

영속성 컨텍스트가 관리하는 엔티티 정보 보관소, 이 보관소에 들어 있는 것이 '영속 상태'

영속 상태는 아직 DB에 저장된 상태는 아니고, 영속성 컨텍스트에서 관리하는 상태를 의미한다. 

Map에는 key는 id값, value는 해당 entity값이 들어있다.

 

특징

  • 1차 캐시는 서로 공유하지 않고 하나의 스레드가 시작부터 끝날때까지 임시로 사용하는 글로벌하지 않은 캐시이다.

예를 들어 100명에게 요청 100개가 오면 엔티티 매니저 100개가 생성되고 1차 캐시에도 100개가 생성된다. 스레드가 종료되면 모두 사라진다.

  • 트랜잭션의 범위 안에서만 사용하는 짧은 캐시 레이어
  • 전체에서 사용하는 글로벌 캐시는 2차 캐시라고 한다.

 

쿼리문 저장소

JPA는 필요한 쿼리문을 보관한다. 쿼리문을 여러가 모아두었다가 DB에 한번에 접근할 수 있어 성능에 이점이 있다.

flush()를 사용해 저장해둔 쿼리문으로 DB에 접근한다. 

 

엔티티의 생애 주기

비영속

영속성 컨텍스트와 관계가 전혀 없는 상태

객체를 생성만 하고 영속성 컨텍스트에 저장하지 않은 상태

 

 

영속

영속성 컨텍스트에 저장된 상태, 즉 엔티티가 영속성 컨텍스트에 의해 관리되는 상태

persist(entity); (비영속상태에서 영속 상태로 만드는 함수)

이 함수를 통해 엔티티가 1차 캐시에 저장이 된다 + 엔티티를 저장하는 Insert 쿼리문이 쿼리문 저장소에 생성된다.

하지만 영속 상태라고 바로 DB에 쿼리를 보내지 않는다 ( = DB에 저장되지 않는다)

 

반대로 엔티티매니저가 1차 캐시에 데이터가 없어 DB에서 데이터를 조회하는 데이터도 영속 상태인 데이터이다.

DB에서 조회해온 데이터는 1차 캐시 저장소에 먼저 저장되고, 엔티티정보를 반환한다. 이 때 사용하는 함수는 find()

 

find()로 가져온 데이터를 다시 조회하면 JPA는 1차 캐시 저장소에 있는 엔티티를 반환하고 DB에 접근하지 않는다. (성능상 이점)

그리고 같은 인스턴스의 참조값을 반환하기 때문에 ==로 동일성을 비교해 같은 인스턴스임을 확인한다.(중복 객체 식별)

 

DB

flush()

모아둔 쿼리문을 flush() 를 실행하면 DB에 반영한다. 이 이후에도 1차 캐시 저장소에 엔티티들은 남아있다.

왜냐하면 flush()는 영속성 컨텍스트와 DB를 동기화하는 역할을 한다.

 

과정

 

1) 변경감지를 통해 영속상태의 엔티티중 속성이 변경된 엔티티를 update한다.

2) 쓰기 지연 쿼리 동기화

쓰기 지연 사용 이유 : 쿼리를 한번에 보내는 것이 쿼리를 여러번 보내는 것보다 성능이좋다.

 

flush()는 1차 캐시를 지우지 않고 쿼리를 DB에 날려서 DB와의 싱크를 맞추는 역할을 한다. 쿼리를 보내고 난 후에 commit()을 실행한다.

 

플러시 flush()'가 호출되고 실행하기 직전에, 엔티티 매니저는 복사본(Snapshot)과 실제 엔티티를 비교한다. 만약 저장 해둔 복사본과 실제엔티티를 대조했을 때, 내용이 다르다면 (필드값이 다르다면) 엔티티 매니저는 '변경을 감지' 할 수 있다.  이 경우 적절한 UPDATE 문을 생성하고 플러시와 함께 쿼리문을 던져준다.

 

commit 

트랜잭션을 begin()으로 수행하고 commit()을 해 트랜잭션을 종료한다. commit을 사용해 내부적으로 Entity매니저의 flush를 호출하고 트랜잭션을 닫는다.

 

commit vs flush

flush는 쿼리를 전송하는 역할이고 commit은 flush를 수행한 뒤 트랜잭션을 끝내는 역할을 한다.

flush로 전송된 쿼리는 rollback할 수 있지만, commit은 할 수 없다.

 

변경 감지(dirty checking)

EntityManager 에는 INSERT, DELETE, SELECT 를 담당하는 메서드는 존재하지만 UPDATE에 관련된 메서드는 없다.

왜냐하면 1차 캐시에 존재하는 엔티티는 속성의 변경이 이루어지면 UPDATE를 flush() 시점에 수행하기 때문이다.

 

스냅샷

1차 캐시에 엔티티가 저장될 경우 엔티티를 처음 영속 상태로 만들었을 때의 복사본(스냅샷)을 같이 저장한다.

(1차 캐시 저장소에서 영속성 컨텍스트는 엔티티의 @id 값으로 구별한다. 같은 아이디를 가진 다른 엔티티 인스턴스가 있어도 같은 엔티티로 취급한다)

준영속

영속성 컨텍스트에 저장되었다가 분리된 상태, 영속성 컨텍스트에서 지워진 상태 (더티체킹, 업데이트 쿼리 사용 불가)

 

준영속 상태로 만드는 법(영속 -> 준영속) 3가지

em.detach(entity);

특정 엔티티를 준영속 상태로 전환

detach를 사용하면 영속성 컨텍스트에서 분리함을 의미, jpa가 관리하지 않는 객체가 된다.

엔티티가 변경이 되었는데, 실제로 업데이트 쿼리가 나가지 않음.

 

em.clear();

영속성 컨텍스트를 완전히 초기화 = 영속성 컨텍스트를 모두 지운다.

쿼리들도 모두 초기화된다.

 

em.close();

영속성 컨텍스트를 종료한다. 영속성으로 관리되던 엔티티들도 모두 준영속 상태가 된다.

준영속 상태의 엔티티들은 엔티티 매니저의 merge()를 사용해 다시 영속 상태로 등록할 수 있다.

 

 

준영속 엔티티를 수정하는 방법

 

1. merge

2. 변경 감지기능

 

merge()

1.  merge()를실행한다

2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.

2-1 1차에 없다면 DB에서 조회해 1차 캐시에 저장한다.

3. 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다. 

4. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해 DB에 UPDATE SQL이 실행

 

주의변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, merge를 사용하면 모든 속성이 변경된다

병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)

 

레포지토리의 save() 메소드

새로운 엔티티 저장과 준영속 엔티티 병합을 편리하게 한번에 처리

만약 식별자 값이 있으면 이미 한번 영속화 되었던 엔티티로 판단해서 merge() 로 수정(병합)한다. (주로 @GeneratedValue를 사용)

반면, 식별자 값이 없으면 새로운 엔티티로 판단해서 persist() 로 호출해 식별자 값이 자동으로 할당된다. 

또 다른 예외로 만약 @Id만 선언하고 식별자를 직접 할당하지 않아 persist()를 호출하면 식별자가 없다는 예외가 발생한다

 

save는 의미는 신규 데이터를 저장하는 것뿐만 아니라 변경된 데이터의 저장이라는 의미도 포함한다. 

이렇게 함으로써 이 메서드를 사용하는 클라이언트는 저장과 수정을 구분하지 않아도 되므로 클라이언트의 로직이 단순해진다.

 

 

영속 상태의 엔티티는 변경 감지(dirty checking)기능이 동작해서 트랜잭션을 커밋할 때 자동으로 수정되므로 별도의 수정 메서드를 호출할 필요가 없고 그런 메서드도 없다.

 

변경 감지 기능

@Transactional
void update(Item itemParam) { // itemParam : 파라미터로 넘어온 준영속 상태의 엔티티
	Item findItem = em.find(Item.class, itemParam.getId()); // 같은 엔티티를 조회한다.
    findItem.setPrice(itemParam.getPrice()); // 데이터를 수정한다.
}

영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정한다.

트랜잭션 안에서 엔티티를 조회하고, 변경할 값을 선택한다. 트랜잭션을 커밋하는 시점에 변경을 감지하고 동작해 DB에 update SQL이 실행된다.

삭제

엔티티를 영속성 컨택스트에서 관리하지 않고 엔티티를 DB에서 삭제하는 DELETE 쿼리문 저장소에 쿼리문을 저장한다.

(flush()가 있어야 DB에 접근해 삭제된다.)

 

 

출처 : https://siyoon210.tistory.com/138

 - 인프런 김영한님 강의

 

 

'Spring' 카테고리의 다른 글

@Transactional 동작 과정  (0) 2024.02.28
OSIV 동작 과정  (0) 2024.02.28
스프링을 공부한다면 이정도는 - 스프링 동작 원리  (0) 2024.02.03
상속 관계 매핑을 넘어..  (0) 2023.03.24
일급 컬렉션이란?  (0) 2023.02.01
복사했습니다!