트랜잭션 적용
트랜잭션 적용 확인
@Transactional을 통해 선언전 트랜잭션 방식을 사용하면 어노테이션 하나로 트랜잭션을 적용할 수 있다. 그런데 이 기능은 트랜잭션 관련 코드가 눈에 보이지 않고 AOP를 기반으로 동작하기 때문에 실제 트랜잭션이 적용되고 있는지 확인하기가 어렵다. 확인하는 방법을 알아보자.
테스트 코드
@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();
}
}
@Slf4j
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():@Transactional을 메서드나 클래스에 붙이면 해당 객체는 AOP 적용의 대상이 되고 실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록된다. 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입된다.실행 결과

@Transactional어노테이션이 특정 클래스나 메서드에 하나라도 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록한다. 그리고 실제 객체 대신에{객체 이름}$$CGLIB를 스프링 빈에 등록한다. 그리고 프록시는 내부에 실제 객체를 참조하게 된다. 실제 객체 대신에 프록시가 스프링 컨테이너에 등록이 되는 것이다.클라이언트 코드는 DI를 할 때 스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시를 주입한다.
프록시는 실제 객체를 상속해서 만들어지기 때문에 다형성을 활용할 수 있다.

로그 확인 설정
트랜잭션 프록시가 호출하는 트랜잭션의 시작과 종료를 명확하게 로그로 확인할 수 있다.
basicService.tx() 호출
클라이언트가 이 메서드를 호출하면 프록시의 메서드가 호출되고 여기서 프록시는 메서드가 트랜잭션을 사용할 수 있는지 확인해본다.
@Transactional이 붙어있으므로 트랜잭션 적용 대상이다.트랜잭션을 시작한 다음에 실제 객체의 메서드를 호출한다.
그리고 실제 객체의 호출이 끝나서 프록시로 리턴 되면 프록시는 트랜잭션 로직을 커밋하거나 롤백 해서 트랜잭션을 종료한다.
basicService.nonTx() 호출
마찬가지로 이 메서드가 트랜잭션을 사용할 수 있는지 확인해본다.
@Transactional이 없으므로 트랜잭션 적용 대상이 아니다.트랜잭션을 시작하지 않고 실제 객체의 메서드를 호출하고 종료한다.
TransactionSynchronizationManager.isActualTransactionActive(); : 현재 쓰레드에 트랜잭션이 적용되어 있는지 확인할 수 있다.
실행 결과
트랜잭션 적용 위치
스프링에서 우선순위는 항상 더 구체적이고 더 자세한 것이 높은 우선순위를 가진다. 메서드와 클래스 중 더 구체적인 메서드가 더 높은 우선순위를 가진다.
테스트 코드
스프링의 @Transactional 2가지 규칙
우선순위 규칙
트랜잭션을 사용할 때는 다양한 옵션을 사용할 수 있는데
LevelService에 클래스와 메서드에 다른 옵션이 붙어있다. 클래스 보다는 메서드가 더 구체적이므로write()에는readOnly=false옵션이 적용이 된다.
클래스에 적용하면 메서드는 자동 적용
read()메서드에는@Transactional이 없기 때문에 더 상위인 클래스를 확인한다. 클래스에readOnly=true이 적용되어 있기 때문에read()메서드도 따라서 받게 된다.
readOnly=false가 default값이다.TransactionSynchronizationManager.isCurrentTransactionReadOnly();: 현재 트랜잭션에 적용된readOnly옵션 값을 반환한다.
실행 결과
인터페이스 @Transactional
인터페이스에 적용 시 우선순위
클래스의 메서드
클래스 타입
인터페이스의 메서드
인터페이스 타입
구체적인 것이 더 높은 우선순위를 가진다고 생각하면 이해하기 쉽다.
Last updated