본문 바로가기

IT/JPA

JPA EntityManager 라이프사이클에 대해 알아보자

이번 시간에는 JPA Lifecycle에 대해 간단히 알아보고자 합니다.

저 같은 경우는 설정파일로 만들어 사용하고 있는데 오늘 그 개념을 정리하고자 글을 적습니다.
예전 강의를 들을때를 기억하며 정리하도록 하겠습니다.

- JPA 코드

  • Spring Data Jpa가 아닌 JPA에서의 코드를 먼저 한번 보겠습니다.
emf: EntityManagerFactory = Persistence.createEntityManagerFactory("master")
entityManager: EntityManager = emf.createEntityManager()
transaction: EntityTransaction = entityManager.getTransaction()

try {
    transaction.begin()
    ...
    transaction.commit()
} catch (Exception e) {
    transaction.rollback()
}

entityManager.close()
emf.close()

EntityManagerFactory

  • 엔티티 매니저를 생성하기 위한 클래스입니다.
  • Thread-safe하기 때문에 DB당 하나만 생성해서 공유해야 합니다.
  • @configuration 애노테이션 사용해 싱글톤으로 등록해 줄 수 있습니다.

EntityManager

  • Entity의 생명 주기와 트랜잭션 등을 관리하며, 영속성 컨텍스트에 접근하는 객체입니다.
  • 가급적 쓰레드 간에 공유해서는 안되고(동시성 문제), 사용하고 나면 바로 버려야 합니다.
  • Thread-safe하지 않기 때문에 상황에 따라 매번 새로운 엔티티매니저를 만듭니다.
  • 엔티티 매니저당 영속성 컨텍스트를 하나씩 가집니다.
  • 엔티티 매니저당 트랜잭션도 하나씩 가집니다. (getTransaction()을 여러번해도 같은 객체를 반환합니다.)

- Spring Data JPA

  • 위에서 Spring JPA에서의 EntityManger의 코드를 살펴봤습니다. 이제 Spring Data JPA에서는 EntityManger를 만들고 Transaction을 생성하는지 알아보겠습니다.

PlatformTransactionManager

각 DB마다 트랜잭션 접근 방법이 다르기 때문에 트랜잭션 접근의 추상화가 필요합니다.

PlatformTransactionManager이 이 부분을 추상화를 해줍니다.

  1. 트랜잭션 추상화
  2. 리소스 동기화

트랜잭션 동기화 흐름

  • Transaction을 위해서는 Connection이 이루어져야 합니다.
    트랜잭션 매니저는 DataSource로 Connection을 얻고 Transaction을 시작합니다.
  • 트랜잭션 매니저는 Connection을 트랜잭션 동기화 매니저로 전달합니다.
  • Repository는 Transaction 에서 커넥션을 꺼내 사용하고 Transaction이 종료되는 시점에
    트랜잭션 매니저트랜잭션 동기화 매니저에 보관된 Connection을 통해 Transaction을 종료하고
    Connection을 닫아줍니다.

기능

  • 트랜잭션을 시작합니다.
  • 트랜잭션을 시작하면 다음 세 가지 일이 발생됩니다.
    • Connection을 얻어옵니다.
    • Connection에 set autoCommit False를 설정합니다.
    • Connection을 트랜잭션 동기화 매니저에 넣습니다.
  • 트랜잭션을 시작하면 TransactionStatus status가 반환됩니다.
    • TransactionStatus에는 현재 트랜잭션의 상태 정보가 포함되어 있습니다.
    • Commit / RollBack에 필요합니다
  • new DefaultTransactionDefinition()을 이용해 트랜잭션과 관련된 옵션을 지정할 수 있습니다.
  • 트랜잭션 매니저는 Commit을 통해서 현재 트랜잭션을 커밋함으로 트랜잭션의 상태를 전달해야 합니다.
  • Commit 후
    • Connection리소스가 반환
    • 리소스가 릴리즈
    • 트랜잭션 매니저에서 커넥션 삭제
    • 커넥션 풀 반납
  • Rollback
    • 트랜잭션시 문제가 생기면 Status를 전달해 트랜잭션을 처음으로 돌립니다.

JpaTransactionManager

기능

  • 공식문서에 따르면 PlatformTransactionManager 단일 JPA 구현 EntityManagerFactory 지정된 팩터리에서 스레드로 JPA EntityManager를 바인딩하여 잠재적으로 팩터리당 하나의 스레드 바인딩된 EntityManager를 허용합니다.라고 나와있습니다.

JpaTransactionManager클래스를 살펴보면 EntityManagerFactory를 볼 수 있습니다.

EntityManagerFactory는 기본적으로 JpaBaseConfiguration.java 에서 bean으로 등록되어 주입됩니다.

단 별도의 EntityManagerFactory 를 구현해서 bean으로 등록했다면 해당 config는 동작하지 않습니다

  • @Transactional 애노테이션을 사용해 method레벨, service레벨에서 Transaction이 제대로 동작하는 것을 확인 하실 수 있습니다.

JPATransactionManger에서 EntityManagerFactory 활용

JpaTransactionManager에서 EntityManagerFactory는 doBegin() 메소드가 실행되는 순간부터 사용됩니다.

JpaTransactionManager의 doBegin()의 일부

doBegin()의 createEntityManagerForTransaction()의 일부

EntityManagerFactory와 EntityManager 동작 흐름

  1. 스프링 구동시 JpaBaseConfiguration에 의해 EntityManagerFactory가 Singleton으로 Bean에 등록됩니다.
  2. @Transactional 애노테이션이 실행될 경우 EntityManagerFactory에서 EntityManager를 개별 생성
  3. 만들어진 EntityManager각 @Transaction 애노테이션 별로 Transaction을 관리하게 됩니다.

@Transaction 애노테이션이 Service안에 하나 더 설정되면 어떻게 되는 지 궁금해서 알아봤습니다.

@Service
class GommiService(
  val subGommiService : SubGommiService
) {

  @Transactional
  fun overlapTransactionTest() {
    subGommiService.gommiChild()
  }

}
@Service
class subGommiService {
  @Transactional
  fun gommiChild() {
    println("곰이야 사랑해")
  }
}

중첩된 Transaction을 확인 해보겠습니다.

GommiService.overlapTransactionTest()

  1. @Transactional이기 때문에 Component가 등록될때 Proxy로 감싸져있고 JpaTransactionManager의 부모인AbstractPlatformTransactionManager의 getTransaction()가 호출됩니다.
  2. 기존의 실행되고 있는 EntityManager 또는 Transaction이 없으니깐 createEntityManagerForTransaction()가 호출되고 EntityManager가 생성됩니다.
  3. 추가적인 EntityManger생성시 ThreadLocal에 Map형태로 저장되고 EntityManagerFactory, EntityManager 값을 key : value 로 저장됩니다.

subgommiService.gommiChild()

  1. @Transactional 애노테이션에 의해 getTransaction()가 호출됩니다.
  2. JpaTransactionManager의 doGetTransaction메소드로 인해 실행되고 있는 트랜잭션을 가져와서 사용하게 됩니다.

  1. subGommiSerivce.gommiChild()는 부모와 같은 EntityManagerFactory를 사용하므로 GommiService의 EntityManager를 가져와서 사용하게 됩니다.
  2. 부모와 같은 EntityManager를 사용하고 같은 getTransaction()을 호출하기 때문에 같은 Transaction으로 묶여집니다.

- Transactional 옵션

Propagation.REQUIRED (기본값)

@Transactional(propagation = Propagation.REQUIRED)
public void doSomething() { ... }
  • 별도의 트랜잭션을 설정하지 않으면 트랜잭션을 새로 시작합니다.
  • 이미 트랜잭션이 설정되어 있으면 기존 트랜잭션 내에서 로직을 실행합니다.

Propagation.REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() { ... }
  • 이름에서 유추할 수 있듯이 매번 새로운 트랜잭션을 시작합니다.
  • 이미 트랜잭션이 설정되어 있으면 트랜잭션 메서드가 종료될 때까지 대기상태 있다가 실행합니다.
  • 트랜잭션이 독립적으로 작동합니다.

Propagation.NESTED

@Transactional(propagation = Propagation.NESTED)
public void doSomething() { ... }
  • 데이터베이스가 SAVEPOINT기능을 지원할 경우 지정한 시점까지 부분 롤백이 가능하다.
  • Propagation.REQUIRED와 동일하게 작동합니다.

정리

오늘은 EntityManager를 JPA, Spring Data Jpa로 분리해서 알아봤습니다.

참고로 저는 Spring Data JPA만 사용해서 이전 인강으로 들었던 순수 JPA에서의 사용을 정리할 수 있는 시간이 되었습니다.