본문 바로가기

framework/spring boot

JPA EntityManager 영속성 컨텍스트 @PersistenceContext

1. 영속성 컨텍스트란?

영속성 컨텍스트(Persistence Context)는 JPA에서 'Entity를 영구 저장하는 환경'이라고 해석할 수 있다.

EntityManagerFactory에서 생성된 EntityManager로 Entity를 관리(저장, 조회 등)할 때 영속성 컨텍스트에 엔티티를 보관하고 관리한다.


2. Entity 생명주기

Entity는 비영속, 영속, 준영속, 삭제 4개의 상태가 있다.

생명 주기

비영속

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

쉽게 말해 Entity 객체를 persist하지 않아 영속성 컨텍스트에 저장되지 않은 상태이다.

Member member = new Member();
member.setName("test");
//entityManager.persist(member);    //아직 persist하지 않음
영속

Entitiy가 영속성 컨텍스트에 저장되어 관리될 때 영속 상태라한다.

즉, 객체를 persist메소드를 통해 등록할 때 영속 상태가 된다.
또한 find메소드를 통해 영속성 컨텍스트에 존재하지 않는 데이터를 DB에서 불러온 Entity도 영속 상태가 된다.

Member member = new Member();
member.setName("test");
entityManager.persist(member);    //영속 상태
준영속

영속 상태의 엔티티를 영속성 컨텍스트에서 제거하면 준영속 상태가 된다.

EntityManager의 detach메소드를 통해 특정 엔티티를 준영속 상태로 만들 수 있다.

Member member = new Member();
member.setName("test");
entityManager.persist(member);
entityManager.detach(member);    //준영속
삭제

Entity를 영속성 컨텍스트와 데이터베이스에서 제거한다.

EntityManager의 remove메소드를 통해 삭제한다.

Member member = new Member();
member.setName("test");
entityManager.persist(member);
entityManager.remove(member);    //준영속

3. 영속성 컨텍스트 특징

1차 캐시

먼저 영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다. 영속 상태인 Entity는 모두 이곳에 저장된다. 다시 말해서 persist를 사용해 영속 상태를 만들면 1차 캐시에 저장된다.

캐시는 Map으로 되있고 식별자를 KEY로하고 Entity가 Value가 된다. 식별자는  DB 기본키와 매핑된다.

1차 캐시

 

Entity 조회

Entity를 find하게 되면 JPA는 기본적으로 1차 캐시를 먼저 찾는다. 만약 1차 캐시에 Entity가 없다면 DB에서 조회한다. 이후 DB에서 조회한 데이터를 1차 캐시에 저장하고, Entity를 반환한다. 물론 여기서 Entity는 영속 상태이다.

JPA DB 조회

 

영속 상태 Entity의 동일성 보장
Member a = em.find(Member.class, 1);
Member b = em.find(Member.class, 1);
System.out.println(a == b);

위 코드의 결과는 참일까 거짓일까?

영속성컨텍스트의 캐시에서 Entity를 계속 가져오기 때문에 a와 b는 같은 인스턴스이다.
즉, 영속성 컨텍스트는 성능상 이점과 Entity의 동일성을 보장한다. 

 

Entity 등록

기본적으로 JPA에서는 트랜잭션을 commit하기 직전까지 Entity를 DB에 저장하지 않고, 내부 쿼리 저장소에 INSERT구문을 모아둔다. 이것을 트랜잭션을 지원하는 쓰기 지연이라 한다. commit을하게 되면 flush()를 실행해 DB로 INSERT구문을 보내게 된다.

영속상태의 Entity가 commit후의 동작

Entity 수정(변경 감지)

update문을 작성하는 SQL과 달리 JPA에서는 변경감지라는 기능을 이용해 update가 진행된다.

Entity가 영속성 컨텍스트에 보관될 때, 최초 상태를 복사해서 저장하게 되는데 이것을 스냅샷이라 한다.

변경감지는 flush가 실행될 때 스냅샷과 현재 Entity를 비교하여 변경된 Entity를 찾고 수정 쿼리를 생성 SQL 저장소에 보내고 이것을 DB로 보낸다.

여기서 중요한건 변경 감지는 영속 상태의 Entity만 적용된다.

Entity 수정

Entity 삭제
Member member = em.find(Member.class, 1);
em.remove(member);

삭제는 remove() 메소드를 이용한다. 삭제 동작이 되면 영속성 컨텍스트에서 제거된다. 또 삭제 동작후 바로 DB에서 제거되지 않고 SQL저장소에 삭제 쿼리가 저장되었다가 flush호출때 DB로 전달한다. 

 

Flush

위에서 계속 봤듯이 Flush를 실행하면

  • 쓰기 지연 SQL 저장소의 쿼리를 DB로 전송
  • 변경 감지가 동작해 영속성 컨텍스트의 모든 Entity를 스냅샷과 비교해 수정된 Entity 탐색후 수정쿼리를 만들어 SQL저장소에 저장

Flush를 사용하는 방법은 Flush를 직접 호출, 트랜잭션 커밋시 자동호출, JPQL 쿼리 실행 시 자동호출 세가지가 있다.

참고로 find() 메소드에는 Flush가 실행되지 않는다.

 

Entity 준영속

준영속은 영속 상태인 Entity가 영속성 컨텍스트에서 분리된 것을 준영속이라 위에 설명했다.

준영속 상태를 만드는법은 크게 3가지가 있다.

  • EntityManager.detach(Entity): 특정 Entity를 준영속상태로 전환
  • EntityManager.clear(): 영속성 컨텍스트를 초기화
  • EntityManager.close(): 영속성 컨텍스트 종료

detach()메소드를 실행하게 되면 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 Entity관련된 정보가 모두 제거된다.

clear()메소드는 영속성 컨텍스트안에 있는 모든 Entity 준영속 상태로 만든다. 즉, 모든 Entity가 detach()된다 생각하면 된다.

close()메소드는 영속성 컨텍스트를 종료해 관리하던 Entity를 준영속 상태로 만든다.

Entity merge

merge는 준영속 상태의 Entity를 다시 영속 상태로 만들 수 있다.

//member Entity가 준영속이라 가정
Member mergeMember = EntityManager.merge(member);

여기서 중요한건 member Entity를 merge한다해서 member 자체가 다시 영속 상태가 되는 것이 아니라 mergeMember라는 새로운 영속 상태의 엔티티가 반환된다.

또한 merge는 비영속 Entity도 영속 상태로 만들 수 있다. 원리는 merge의 paramter의 식별자로 영속성 컨텍스트를 조회하고 Entity가 없다면 DB를 조회한다. DB에서도 만약 찾지 못하면 새로운 Entity를 생성해 병합한다.

따라서 merge는 save or update 기능을 수행할 수 있다.

 

다음은 merge 동작 방식이다.

merge 동작 방식

주의할 점은 merge는 모든 필드를 교체하기에 null로도 변경될 가능성이 있기 때문에 잘 확인하고 사용해야한다.

'framework > spring boot' 카테고리의 다른 글

@RequiredArgsConstructor 의존성 주입  (0) 2022.03.04
Spring boot 연관관계 매핑  (0) 2022.02.28
Spring boot JPA @Embedded, @Embeddable  (0) 2022.02.28