트랜잭션 적용 확인
@Transactional 을 통해 선언적 트랜잭션 방식을 사용하면 단순히 애노테이션 하나로 트랜잭션을 적용할 수 있다.
그런데 이 기능은 트랜잭션 관련 코드가 눈에 보이지 않고, AOP를 기반으로 동작하기 때문에, 실제 트랜잭션이 적용되고 있는지 아닌지를 확인하기가 어렵다.
아래 코드를 확인하여 트랜잭션이 실제 적용되는지 확인해보자.
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Slf4j
@SpringBootTest
public class TxBasicTest {
@Autowired
BasicService basicService;
@Test
void proxyCheck() {
log.info("aop class={}", basicService.getClass());
Assertions.assertThat(AopUtils.isAopProxy(basicService)).isTrue();
}
@Test
void txTest() {
basicService.tx();
basicService.nonTx();
}
@TestConfiguration
static class TxApplyBasicConfig {
@Bean
BasicService basicService() {
return new BasicService();
}
}
@Slf4j
static class BasicService {
@Transactional
public void tx() {
log.info("call tx");
boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", actualTransactionActive);
}
public void nonTx() {
log.info("non call tx");
boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", actualTransactionActive);
}
}
}
application.properties
logging.level.org.springframework.transaction.interceptor=TRACE
이 로그를 추가하면 트랜잭션 프록시가 호출하는 트랜잭션의 시작과 종료를 명확하게 로그로 확인할 수 있다.
위 테스트 코드에서는 트랜잭션이 실제 적용되는지 확인할 수 있는 BasicServie를 테스트 클래스 내부에 만들고 TxApplyBasciConfig를 통해 Bean으로 만들어 Autowired를 이용해서 사용할 수 있도록 했다.
proxyCheck() - 실행
AopUtils.isAopProxy(object) : object가 Aop로 만들어졌는지 확인 할 수 있는 메서드 이다.
선언적 트랜잭션 방식에서 스프링 트랜잭션은 AOP를 기반으로 동작한다.
@Transactional 을 메서드나 클래스에 붙이면 해당 객체는 트랜잭션 AOP 적용의 대상이 되고, 결과적으로 실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록된다.
그리고 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입된다. 클래스 이름을 출력해보면 basicService$$EnhancerBySpringCGLIB... 라고 프록시 클래스의 이름이 출력되는 것을 확인할 수 있다.
proxyCheck() - 실행 결과
TxBasicTest : aop class=class ..$BasicService$$EnhancerBySpringCGLIB$ $xxxxxx
txTest() 실행
실행 결과
#tx() 호출
TransactionInterceptor : Getting transaction for [..BasicService.tx]
y.TxBasicTest$BasicService : call tx
y.TxBasicTest$BasicService : tx active=true
TransactionInterceptor : Completing transaction for
[..BasicService.tx]
#nonTx() 호출
y.TxBasicTest$BasicService : call nonTx
y.TxBasicTest$BasicService : tx active=false
로그를 통해 tx() 호출시에는 tx active=true 를 통해 트랜잭션이 적용된 것을 확인할 수 있다.
TransactionInterceptor 로그를 통해 트랜잭션 프록시가 트랜잭션을 시작하고 완료한 내용을 확인할 수 있다.
nonTx() 호출시에는 tx active=false 를 통해 트랜잭션이 없는 것을 확인할 수 있다
스프링 컨테이너에 트랜잭션 프록시 등록

@Transactional 애노테이션이 특정 클래스나 메서드에 하나라도 있으면 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록한다.
그리고 실제 basicService 객체 대신에 프록시인 basicService$$CGLIB 를 스프링 빈에 등록한다.
그리고 프록시는 내부에 실제 basicService 를 참조하게 된다.(target)
여기서 핵심은 실제 객체 대신에 프록시가 스프링 컨테이너에 등록되었다는 점이다.
클라이언트인 txBasicTest 는 스프링 컨테이너에
@Autowired
BasicService basicService
로 의존관계 주입을 요청한다.
스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시를 주입한다.
프록시는 BasicService 를 상속해서 만들어지기 때문에 다형성을 활용할 수 있다.
따라서 BasicService 대신에 프록시인 BasicService$$CGLIB 를 주입할 수 있다
트랜잭션 프록시 동작 방식

tx()의 경우 @Transactional 애노테이션이 붙어있어서 트랜잭션의 적용 대상이 된다.
그러므로 tx()를 호출했을때 트랜잭션 관련 기능들이 동작하게 되고 비지니스 로직이 있는 실제 basicService를 호출한다.
그러나 nonTx()의 경우 @Transactional 애노테이션이 붙어있있지 않아 트랜잭션의 적용 대상이 되지 않는다.
그러므로 nonTx()를 호출하게 되면 트랜잭션 관련 기능들이 동작하지 않게 되고 비지니스 로직이 있는 실제 basicService를 바로 호출한다.
References 및 사진 출처
김영한의 스프링 DB 2편 - 데이터 접근 활용 기술
'Spring > Transaction' 카테고리의 다른 글
| Spring Transaction AOP 주의사항 - 프록시 내부호출 문제해결 (0) | 2023.08.15 |
|---|---|
| Spring Transaction AOP 주의사항 - 프록시 내부호출 문제 (0) | 2023.08.15 |
| Spring Transaction 우선순위 (0) | 2023.08.15 |
| Spring Transaction 과 AOP (0) | 2023.08.15 |
| Spring Transaction 이란? (0) | 2023.08.15 |