이번 시간에는 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이 이 부분을 추상화를 해줍니다.
- 트랜잭션 추상화
- 리소스 동기화
트랜잭션 동기화 흐름
- 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 동작 흐름
- 스프링 구동시 JpaBaseConfiguration에 의해 EntityManagerFactory가 Singleton으로 Bean에 등록됩니다.
- @Transactional 애노테이션이 실행될 경우 EntityManagerFactory에서 EntityManager를 개별 생성
- 만들어진 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()
- @Transactional이기 때문에 Component가 등록될때 Proxy로 감싸져있고 JpaTransactionManager의 부모인AbstractPlatformTransactionManager의 getTransaction()가 호출됩니다.
- 기존의 실행되고 있는 EntityManager 또는 Transaction이 없으니깐 createEntityManagerForTransaction()가 호출되고 EntityManager가 생성됩니다.
- 추가적인 EntityManger생성시 ThreadLocal에 Map형태로 저장되고 EntityManagerFactory, EntityManager 값을 key : value 로 저장됩니다.
subgommiService.gommiChild()
- @Transactional 애노테이션에 의해 getTransaction()가 호출됩니다.
- JpaTransactionManager의 doGetTransaction메소드로 인해 실행되고 있는 트랜잭션을 가져와서 사용하게 됩니다.
- subGommiSerivce.gommiChild()는 부모와 같은 EntityManagerFactory를 사용하므로 GommiService의 EntityManager를 가져와서 사용하게 됩니다.
- 부모와 같은 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에서의 사용을 정리할 수 있는 시간이 되었습니다.
'IT > JPA' 카테고리의 다른 글
JPA BatchSize에 대해 알아보자 (0) | 2023.04.18 |
---|---|
JPA의 낙관적 락과 비관적 락을 통해 엔티티에 대한 동시성 제어에 대해 알아보자 (0) | 2023.04.16 |
JPA EntityGraph에 대해 알아보자 (0) | 2023.04.11 |
JPA 상속 전략에 대해 알아보자 (0) | 2023.04.06 |
JPA Entity Manager를 알아보자 (0) | 2023.04.05 |