프로그래밍/JPA

[JPA] 영속성 컨텍스트와 Entity 상태

※ 영속성  컨텍스트란?

영속성 컨텍스트는 간단히 말하면

Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간입니다.

 

JDBC API와 SQL Mapper를 이용하여 개발자들이 직접 쿼리를 작성해

DB에 데이터를 관리해왔던 시절에서 JPA 를 사용하여 쿼리를 작성하지않고

객체를 활용하여 DB에 데이터를 관리할 수 있게 되었습니다. 

이러한 과정을 위해 JPA는 영속성 컨텍스트에 Entity 객체들을 저장하여 관리하며

DB와 소통합니다.

 

 

EntityManger 

영속성 컨텍스트에 접근하여 Entity 객체들을 제어하기 위해서는 EntityManger가 필요합니다.

 

더보기

EntityManager는 이름 그대로 Entity를 관리하는 관리자의 역할을 합니다.

개발자들은 EntityManger를 사용해서 Entity를 저장하고 조회하고 수정하고 삭제할 수 있습니다.

EntityManager는 EntityManagerFactory를 통해 생성하여 사용할 수 있습니다.


영속성 컨텍스트의 기능

1차 캐시

영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있습니다.

  • 우리가 저장하는 Entity 객체들이 1차 캐시 즉, 캐시 저장소에 저장된다고 생각하시면됩니다.
  • 캐시 저장소는 Map 자료구조 형태로 되어있습니다.
    • key에는 @Id로 매핑한 기본 키 즉, 식별자 값을 저장합니다.
    • value에는 해당 Entity 클래스의 객체를 저장합니다.
    • 영속성 컨텍스트는 캐시 저장소 Key에 저장한 식별자값을 사용하여 Entity 객체를 구분하고 관리합니다.

쓰기 지연 저장소 (Action Queue)

  • JPA의 트랜잭션을 학습하면서 JPA가 트랜잭션 처럼 SQL을 모아서 한번에 DB에 반영한다는 것을 배웠습니다.
    • JPA는 이를 구현하기 위해 쓰기 지연 저장소를 만들어 SQL을 모아두고 있다가 트랜잭션 commit 후 한번에 DB에 반영합니다.

변경 감지 (Dirty Checking)

  • 영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면?
    • 하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적입니다.

 

  • JPA에서는 Update를 어떻게 처리하는지 예를 들어보겠습니다.
  • JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장합니다.
    • 트랜잭션이 commit되고 em.flush(); 가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교합니다.
    • 변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기지연 저장소의 SQL을 DB에 요청합니다.
    • 마지막으로 DB의 트랜잭션이 commit 되면서 반영됩니다.
  • 따라서 변경하고 싶은 데이터가 있다면 먼저 데이터를 조회하고 해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영됩니다.
    • 이러한 과정을 변경 감지, Dirty Checking이라 부릅니다.

 

 


Entity의 상태

JPA(영속성 컨텍스트) 입장에서 엔티티의 생명주기를 4가지로 나눌 수 있습니다.

(엔티티는 쉽게 말해 하나의 인스턴스, DB입장에서는 한건의 레코드정도로 이해하면 됩니다.)

영속성 컨텍스트와 Entity 관계에 따른 상태도

비영속 상태

엔티티가 영속성 컨텍스트와 전혀 관련이 없는 상태입니다.

영속(managed) 상태

엔티티가 영속성 컨텍스트에서 관리되고 있는 상태 ( DB에 저장된 상태 X)입니다.

persist(entity) : 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만듭니다.

 

 

위에 이미지는 엔티티를 저장하는 INSERT 쿼리문이 생성 되었지만, 아직 DB에 전달되지 않고 쿼리문 저장소에 보관되었습니다.

'flush()'가 실행되기 전에는 실제 DB에 쿼리를 날리지 않습니다.  

여러개의 엔티티를 persist()하게 되더라도, 해당하는  쿼리문은 보관하게되고 영속상태에 있게 됩니다.

 

 

모아둔 쿼리문은 'flush()'를 실행하게 될 떄 DB에 반영합니다.

flush를 하더라도 1차 캐시 저장소에서 관리중인 엔티티들은 사라지지 않고 존재합니다.

준영속(Detached) 상태

준영속 상태는 영속성 컨텍스트에 저장되어 관리되다가 분리된 상태를 의미합니다.

엔티티를 준영속 상태로 만드는 방법은 3가지가 있습니다

 

1. 특정 엔티티를 준영속 상태로 만들기 위해서는 EntityManager의 detach()를 사용합니다.

em.detach(entity2)

 

 

2. 영속성 컨텍스트 전체를 초기화시키는 clear()를 사용합니다.

영속성 컨텍스트를 초기화하면, 영속 상태였던 entity들 전부 준영속 상태가 된다.

 

3. 영속성 컨텍스트를 닫아버리는 close()를 사용합니다.

영속성 컨텍스트 자체가 사라지게되어, 관리되던 entity들은 모두 준영속 상태가 됩니다.

영속성 컨텍스트가 종료되었기 떄문에 영속성 컨텍스트를 사용할 수 없습니다.

 

 

삭제(removed) 상태

삭제 상태는 entity를 영속성 컨텍스트에서 관리하지 않게 되고, 해당 엔티티를 DB에서 삭제하는 DELETE 쿼리문을 보관하게 됩니다.

persist와 마찬가지로 'flush()' 가 실행되기 전까지는 DB에 접근하지 않습니다.


추후 공부해야할 주제들

1. flush()가 실행되는 시점

2. 지연 로딩과 즉시 로딩

3. OSIV (Open Session In View) - 영속성 컨텍스트의 생존범위를 트랜잭션 범위가아닌 스프링의 View 영역까지 연장시키는 옵션.


* 참고한 자료들

728x90