본문 바로가기

연재작/Database

Persistence Context의 Entity 상태 관리, Life-Cycle

영속성 컨텍스트(Persistence Context)는 JPA에서 Entity의 관리를 하기 위한 중요한 factor이다.

이를 통해서 우리는 객체 지향적인 entity의 관리만으로도 DB의 조작이 가능해진다.

 

그렇다면 Entity는 영속성 컨텍스트에서 어떤 식으로 관리가 되는지에 대해서 써볼 것이다.

또한, Spring Boot, Tomcat 서버에서 영속성 컨텍스트에서 entity를 관리하기 위한 EntityManager와

Transaction, Thread, Connection, 그리고 영속성 컨텍스트가 어떤 생명주기를 갖고 있는지

이에 대해서 알아보도록 하겠다.

 

 

1. Entity의 4가지 상태

 

  • Transient : 비영속 상태
    • DB에도, 영속성 컨텍스트에도 존재하지 않는 Entity
    • new 생성자를 통해 새롭게 만들어서 DB에 업데이트 할 Entity를 말한다. (아직 persist 전)
    • 데이터베이스와 전혀 연결되어 있지 않은 상태
  • Detached : 준영속 상태
    • 한때는 영속 상태였으나, 현재는 영속성 컨텍스트에 존재하지 않는 Entity
    • 변경 사항이 데이터베이스와 동기화되지 않는다.
    • 다만, new 생성자에 DB에 있는 ID를 집어넣은 상태의 Entity를 만들 경우,
      이때의 entity는 DB의 조작을 INSERT가 아닌 UPDATE가 되기에 예외적으로 이를 detached로 취급한다.
  • Persistent : 영속 상태
    • Entity가 영속성 컨텍스트에 포함됐고, JPA가 관리하는 상태.
    • 변경사항이 자동적으로 추적된다.
  • Removed : 삭제 상태
    • Entity가 삭제 예약 된 상태
    • 트랜잭션 커밋 시 데이터베이스에서 삭제된다.

 

 

2. Entity의 상태를 변경시키는 EntityManager의 메서드들

 

1) persist(entity) 비영속 -> 영속

 

2) find(entityClass.class, PK) DB -> 영속

 

3) merge(entity) 준영속/비영속 -> 영속

 

⭐단, 반환되는 객체가 영속된 객체이다. 인자로 집어넣은 기존의 객체는 여전히 준영속/비영속 상태를 유지 한다.

 

예를 들어, merge를 할 경우에 entity = new User(null, name) 일 경우 id 가 null 인데

merge만 할 경우에는 해당하는 entity라는 객체는 영속성 컨텍스트에서 관리되는 객체는 아니다.

그렇지만 retrived = entityManager.merge(entity) 라는 객체가 영속성 컨텍스트에서 관리되는 객체이다.

 

4) detach(entity) 영속 -> 준영속

 

5) remove(entity) 영속 -> 삭제

 

6) clear() 영속성 컨텍스트를 비운다.

 

 

 

3. Spring에서 Entity Manager의 Bean

 

요청이 들어오면, Tomcat은 스레드 풀(Thread Pool)에서 하나의 스레드를 할당하여 요청을 처리한다.
이 스레드는 Spring MVC의 Front Controller 역할을 하는 DispatcherServlet으로 요청을 전달한다.
DispatcherServlet은 Servlet 컨테이너 내에서 유일하게 등록된 Spring Bean이며, 모든 요청의 진입점 역할을 한다. DispatcherServlet은 HandlerMapping을 통해 요청 URI와 HTTP 메서드에 따라 매핑된 Controller와 메서드를 찾는다.
이후 HandlerAdapter를 통해 매핑된 메서드를 호출하고, 처리 결과를 ViewResolver를 사용해 View로 변환하여
클라이언트에게 응답한다. 이 과정에서 Controller, Service, Repository 등의 객체는 Spring 컨테이너에서 관리된다.
Spring Bean은 일반적으로 Singleton으로 관리되며, 애플리케이션 전역에서 하나의 인스턴스만 생성된다.
이러한 Singleton 객체들은 멀티스레드 환경에서도 안전하게 사용될 수 있도록 설계되었다.

 

 

Spring Data JPA 환경에서는 EntityManager를 따로 Bean으로 등록하지 않더라도,

각각의 @Component에 EntityManager의 주입을 원하면 이를 주입해주기에

나는 여태까지 EntityManager가 하나의 singleton 객체로 사용되는 줄 알았고 이것 때문에 혼란이 왔었다.

EntityManager가 트랜잭션과 영속성 컨텍스트보다 먼저 생성이 되는 것처럼 보였기 때문이다.

매번 요청이 들어올 때 마다 EntityManager를 toString()을 통해 정보를 확인해보면

동일한 EntityManager가 log에 확인이 되기 때문이었다.

 

그러나 자세히 보면 앞에 Shared EntityManager proxy라고 적혀있기에 찾아보니

실제 사용 방식은 그렇지 않았다. 실제로 등록된 EntityManager라는 Bean은 JPA EntityManager가 아닌,

JPA EntityManager의 Proxy 객체였다. 그렇기 때문에 매번 log를 찍어볼 때 마다 같은 값이 나왔던 것이다.

 

실제 동작 방식은 저렇게 만들어진 Proxy 객체가 현재의 트랜잭션 컨텍스트,

영속성 컨텍스트에 맞춰서 EntityManager를 새로 생성 후 할당하는 방식으로 작용했다. (아래의 글 링크 참조)

 

외부적으로는 같은 EntityManager를 사용하는것 처럼 보여도,

내부적으로는 다른 JPA EntityManager를 사용할 수 있다는 것이다. 

그렇기에 EntityManager 자체는 요청이 들어오자마자 사용하는 것을 볼 수 있다.

@GetMapping("/{id}/test")
public ResponseEntity<User> getTest(@PathVariable Long id) {
    User user = entityManager.find(User.class, id);
    return ResponseEntity.ok(user);
}

이렇게만 해주어도, SELECT 쿼리를 통해 데이터를 받아 올 수 있다는 것이다.

그런데 왜 이게 가능할까? 는 다음 챕터에서 다룰 OSIV와 관련이 있다.

지금 여기서는 Spring boot에서는 EntityManager가 요청때부터 생성된다는 것을 알면 되는 것이다.

 

그리고 OSIV가 off라고 가정할 때의

일반적인 스레드, connection, transaction, 영속성 컨텍스트의 생명주기는 아래의 그림과 같다.

 

 

4. Hibernate의 OSIV

 

 

JPA는 기본적으로 Hibernate를 기본 구현체로 사용한다. 

Hibernate는 Open Session In View라는 기능을 제공하는데, 이 기능이 참 재밌다.

 

Session은 Hibernate가 제공하는 JPA EntityManager의 구현체이다.

View에서도 Session을 Open할 수 있게 한다는 것. 다시 말해 View 레이어에서 까지

entity와 관련된 작업을 하게 해준다는 것이며,

이는 다시 말해서 View에서까지 영속성 컨텍스트에 접근이 가능하게 된다는 것이다.

 

이는 @Transactional, 즉 트랜잭션이 열린 범위를 벗어나

controller와 view에서 Lazy Loading이 필요할 때 빛을 발하는 기능이다.

 

Lazy Loading은 한번에 Join을 해서 관련된 entity의 추가정보를 받아오는게 아니라,

필요할 때 받아오는 기능이다. 그런데 이는 영속성 컨텍스트와 EntityManager를 통해서 이루어진다.

따라서 OSIV가 off상태라면, @Transactional의 범위 바깥에서는 Lazy Loading이 일어나지 않는다.

 

하지만 Spring boot는 따로 설정을 하지 않는다면 OSIV가 on인 상태가 된다.

그렇기에 view, controller와 같은 presentation Layer에서도

트랜잭션이 없이, Lazy Loading을 통한 읽기 작업이 가능하다.

(DB에 변화를 주는 CUD 작업들은 트랜잭션이 필요하다.)

 

따라서 Tomcat서버에 요청이 들어온다면, 요청에 대한 스레드가 할당 되고,

connection을 할당 받아 영속성 컨텍스트와 EntityManager를 미리 형성해둔다.

그러면 아래의 그림과 같은 생명주기를 가지게 된다.

 

 

다만 이렇게 설정되어있는 상황이라도, @Transactional을 통해 controller에서 CUD 작업을 하려면

할 수 있고, 해당 레이어에서도 transaction이 유지된다.

다만 이러면 spring MVC에서 controller의 역할 분리가 제대로 이뤄지지 않는 것이기에 추천하지는 않는다.

 

 

 

 

https://www.baeldung.com/spring-open-session-in-view

 

https://jiwondev.tistory.com/255

 

스프링JPA의 영속성컨텍스트 (EntityManager)

💭 JPA (하이버네이트)의 영속성 컨텍스트 JPA에서 영속성 컨텍스트는 DB에서 가져온 엔티티를 저장하는 첫번째 메모리 캐시 저장소입니다.이를 EntityManager 객체의 API로 관리할 수 있습니다. 영속

jiwondev.tistory.com

 

https://stackoverflow.com/questions/1069992/jpa-entitymanager-why-use-persist-over-merge

 

JPA EntityManager: Why use persist() over merge()?

EntityManager.merge() can insert new objects and update existing ones. Why would one want to use persist() (which can only create new objects)?

stackoverflow.com

 

https://dzone.com/articles/how-does-spring-transactional

 

How Does Spring @Transactional Really Work?

 

dzone.com

 

'연재작 > Database' 카테고리의 다른 글

Transaction Deep Dive (5)  (0) 2024.11.28
Transaction Deep Dive (4)  (0) 2024.11.26
Transaction Deep Dive (3)  (0) 2024.11.23
Transaction Deep Dive (2)  (0) 2024.11.22
Transaction Deep Dive (1)  (0) 2024.11.20