[SpringDB2] 스프링 트랜잭션 이해
트랜잭션 적용 확인
TxBasicTest
@Slf4j
@SpringBootTest
public class TxBasicTest {
@Autowired BasicService basicService;
@Test
void proxyCheck() {
log.info("AOP class={}", basicService.getClass());
assertThat(AopUtils.isAopProxy(basicService)).isTrue();
}
@Test
void txTest() {
basicService.tx();
basicService.nontx();
}
@TestConfiguration
static class TxApplyBasicConfig {
@Bean
BasicService basicService() {
return new BasicService();
}
}
static class BasicService {
@Transactional
public void tx() {
log.info("call tx");
// 현재 쓰레드에 트랜잭션이 적용되어 있는지 확인할 수 있는 기능
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
public void nonTx() {
log.info("call nonTx");
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
}
proxyCheck()
AopUtils.isAopProxy()
: 선언적 트랜잭션 방식에서 스프링 트랜잭션은 AOP를 기반으로 동작한다.
@Transactional
을 메서드나 클래스에 붙이면 해당 객체는 트랜잭션 AOP 적용의 대상이 되고, 결과적으로 실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록된다.
실행 결과
TxBasicTest : aop class=class ..$BasicService$$EnhancerBySpringCGLIB...
스프링 컨테이너에 트랜잭션 프록시 등록
@Transactional
애노테이션이 특정 클래스나 메서드에 하나라도 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록한다. 그리고 실제 객체 대신에 프록시를 스프링 빈에 등록한다.- 핵심은 실제 객체 대신에 프록시가 스프링 컨테이너에 등록되었다는 점이다.
- 클라이언트인
txBasicTest
는 스프링 컨테이너에@Autowired
로 의존관계 주입을 요청한다.- 스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시를 주입한다.
트랜잭션 프록시 동작 방식
- 클라이언트가 주입 받은
basicService$$CGLIB
는 트랜잭션을 적용하는 프록시이다.
TxTest()
로그 추가
application.properties
logging.level.org.springframework.transaction.interceptor=TRACE
이 로그를 출력하면 트랜잭션 프록시가 호출하는 트랜잭션의 시작과 종료를 명확하게 로그로 확인할 수 있다.
basicService.tx() 호출
- 클라이언트가
basicService.tx()
를 호출하면, 프록시의tx()
가 호출된다. 프록시는tx()
메서드가 트랜잭션을 사용할 수 있는지 확인한다.tx()
메서드에는@Transactional
이 붙어있으므로 트랜잭션 적용 대상이다.
- 따라서, 트랜잭션을 시작한 다음에 실제
basicService.tx()
를 호출한다. - 실제
basicService.tx()
의 호출이 끝나고 프록시로 제어가 돌아오면 프록시는 트랜잭션 로직을 커밋하거나 롤백해서 트랜잭션을 종료한다.
basicService.nonTx() 호출
- 클라이언트가
basicService.nonTx()
를 호출하면, 트랜잭션 프록시의nonTx()
가 호출된다. 프록시는nonTx()
메서드가 트랜잭션을 사용할 수 있는지 확인해본다.nonTx()
에는@Transactional
이 없으므로 적용 대상이 아니다.
- 따라서, 트랜잭션을 시작하지 않고,
basicService.nonTx()
를 호출하고 종료한다.
실행 결과
#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
댓글남기기