1 분 소요


트랜잭션 적용 확인

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...

스프링 컨테이너에 트랜잭션 프록시 등록
12

  • @Transactional 애노테이션이 특정 클래스나 메서드에 하나라도 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록한다. 그리고 실제 객체 대신에 프록시를 스프링 빈에 등록한다.
    • 핵심은 실제 객체 대신에 프록시가 스프링 컨테이너에 등록되었다는 점이다.
  • 클라이언트인 txBasicTest는 스프링 컨테이너에 @Autowired로 의존관계 주입을 요청한다.
    • 스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시를 주입한다.

트랜잭션 프록시 동작 방식
13

  • 클라이언트가 주입 받은 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


<출처 : 인프런 - 스프링 DB 2편 : 데이터 접근 활용 기술(김영한)>

댓글남기기