[Spring] @Transactional과 entityManager 관계 분석하기

임현규·2023년 6월 1일
1

개인 공부

목록 보기
6/11

@Transactional 공부 계기

Transactional은 보통 서비스 레이어에서 메서드에 관용적으로 사용한다. 조회만하는 경우 readOnly=true로 설정하기도 하고 경우에 따라 세부적인 내용을 적용한다.

이번에 Transactional을 분석하려는 이유는 jpa의 entityManager의 생명주기와 더불어 @Transactional과 어떤 관계가 있는지 확인해보기 위해서다. 프로젝트에서 이 문제때문에 얘기지 못한 문제가 발생했었기 때문이다.

@Transactional 어노테이션과 AOP

어노테이션을 메서드에 달아서 사용한다 -> 대부분 AOP

@Transactional 어노테이션은 메서드에 달아서 메서드 로직 수정 없이 transaction을 활용할 수 있도록 한다. 이는 AOP라 할 수 있다. 그렇다면 Spring에서 @Transactional 어노테이션과 AOP를 어떤 방식으로 활용하고 있을까?

AOP를 활용하려면 Advisor가 있어야 한다.

ProxyTransactionManagementConfiguration

@Configuration class that registers the Spring infrastructure beans necessary to enable proxy-based annotation-driven transaction management.

내용을 해석하면 트랜잭션 관리를 위해 프록시 기반 어노테이션이 필요로 하는 spring 구조의 빈 형태로 등록한 Configuration 클래스이다.

Spring AOP에 중요한것 은 PointCut과 Advice이다.

JoinPoint: Advice가 호출되는 위치, 지점
Advice: JoinPoint에서 실행되는 로직
PointCut: JoinPoint의 집합체로 구체적으로 Advice을 어떤식으로 호출할지 상세하게 정의

Bean으로 등록하는 BeanFactoryTransactionAttributeSourceAdvisor 클래스의 내부 구조를 살펴보면 TransactionAttributeSourcePointcut이 있다. 이 클래스 안에는 PointCut에 대해서 세부 구현되어 있다.

Advice는 위에 주어진 코드를 보면 setAdvice라는 메서드를 알 수 있는데 이를 통해 외부에서 setter 주입을 한다. 이 때 타입은 TransactionInterceptor로 해당 타입은 TransactionManager를 주입해서 타입을 정의한다

해당 코드를 보면 TransactionInterceptor는 TransactionManager를 통해 Advice의 상세 로직을 전략 패턴을 활용해 구현했음을 알 수 있다.

전략패턴: 의존성 주입 형태로 구체적인 동작이나 알고리즘같은 로직을 분리해 놓은 패턴. 이를 통해 객체의 유연성을 높이고 API와 구현 로직을 분리하기 때문에 코드를 수정할 필요가 없어 유지보수가 쉬워진다. 많은 개발자들이 흔히 사용하는 패턴

TransactionManager

TransactionManager 클래스는 단순한 마커 인터페이스이다. 이에 대한 세부적인 API는 PlatformTransactionManager 인터페이스에 잘 나와 있다.

해당 클래스는 3개의 메서드 API를 제공한다.

  1. getTransaction(TransactionDefinition)
  2. commit(TransactionStatus)
  3. rollback(TransactionStatus)

이 3개의 메서드만 보더라도 TransactionManager 타입은 Transaction의 핵심 로직이 포함되어 있음을 알 수 있다.

그러나 해당 인터페이스가 구현해야 할 내용이 너무 많은가 보다. 그래서 spring에서는 AbstractPlatformTransactionManager라는 추상 클래스를 통해 템플릿을 만들어뒀다.(spring이 좋아하는 패턴, 이런 패턴은 코드를 뜯어보면 생각보다 자주 볼 수 있다)

위와 같은 추상 클래스를 통해 3개의 api에 대해서 spring에 잘 호환되도록 구현해놓고 세부적인 로직은 abstract protected를 통해 분리하고 있다. 이는 해당 템플릿 클래스를 상속해서 구현한다.

TransactionInterceptor

TransactionManager가 advice 로직을 담당한다면, interceptor는 해당 advice을 어느 시점에 실행할지에 대한 내용이 담겨 있다.

invoke 메서드 내부에서 호출하고 있는 invokeWithinTransaction를 살펴보자

  1. createTransactionIfNessary를 통해 트랜잭션을 생성한다.
  2. invocation.proceedWIthInvocation()을 통해 기존 메서드를 실행한다.
  3. 오류가 발생시 completeTransactionAfterThrowing 메서드를 호출한다
    • exception의 상황에 따라 rollback을 할지 commit을 할지 결정한다
  4. cleanupTransactionInfo를 통해 transaction에 관련된 리소스를 정리한다
    • txInfo의 정보는 여전히 메모리에 살아있다.
    • 트랜잭션 이전의 상태를 만들어 쓰레드 로컬 환경에서드 안정성있게 트랜잭션 처리 위함
  5. txInfo 정보를 활용해 db에 커밋을 요청한다.
  6. 기존 메서드의 결과를 리턴한다

around 형태로 트랜잭션 aop를 처리함을 알 수 있다.

JPA와 Transaction

지금까지 @Transactional 어노테이션을 등록할 때 AOP가 어떤 방식으로 동작하는지 분석했다. 그리고 세부적인 Transaction Advice는 TransacionManager를 통해 바뀐다는 사실을 알았다. JPA에서는 AbstractPlatformTransactionManager를 상속받아 JPA에 호환되는 Transaction을 구현한다.

JpaTransactionManager

해당 코드를 살펴보면 EntityManagerFactory를 주입하고 있음을 알 수 있다. EntityManagerFactory는 Spring에서 bean으로 관리하고 있고, EntityManager의 생성, 삭제와 같은 생명주기를 관리한다.

해당 코드는 어디서 사용하고 있을까?

doBegin() 메서드는 템플릿 클래스에서 startTransaction(), getTransaction()에서 호출해서 사용한다. 이 의미는 다음과 같이 해석할 수 있다.

💡 트랜잭션이 시작될 때 EntityManager가 생성된다.

알 수 있는 흥미로운 사실

트랜잭션이 시작될 때 entityManagerFactory에서 엔티티 매니저를 생성하고 엔티티의 영속성을 관리한다. 그러나 transaction 종료시 commit 이후 모든 영속성 관리는 해제되기 때문에 이를 인자로 다른 트랜잭션 내부에서 사용해도 persist 상태가 아니기 때문에 따로 merge를 해줘야 한다.

아깐 쓴 글과 같은 내용이긴 하지만 A transaction과 B transaction이 서로 다른 엔티티매니저가 관리하고 transaction이 끝나면 소멸하기때문에 서로 공유하지 않는다.

하나의 요청에 2개의 트랜잭션을 사용할 일이 있다면 이러한 사실에 유의해서 사용하도록 하자.

참고

https://cobbybb.tistory.com/25
https://cobbybb.tistory.com/27#TransactionManager%EC%99%80%20EntityManager-1

profile
엘 프사이 콩그루

0개의 댓글