-
트랜잭션(Transaction) 심화BackEnd/Spring DB 2023. 2. 11. 08:50반응형
이번 포스팅부터는 다음 프로젝트를 참고하시면 됩니다.
트랜잭션 적용 확인
@Transactional을 통한 선언적 트랜잭션 방식에서 스프링 트랜잭션은 AOP를 기반으로 동작합니다. @Transactional을 메서드나 클래스에 붙이면 해당 객체는 트랜잭션 AOP 적용의 대상이 되고, 결과적으로 실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록됩니다. 그리고 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입됩니다.
트랜잭션 프록시가 호출하는 트랜잭션의 시작과 종료를 명확하게 로그로 확인하기 위해서는 application.properties에 다음을 추가합니다.
logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG #JPA log logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG logging.level.org.hibernate.resource.transaction=DEBUG #JPA SQL logging.level.org.hibernate.SQL=DEBUG
다음은 현재 쓰레드에 트랜잭션이 적용되어 있는지 확인할 수 있는 기능입니다. 결과가 true이면 트랜잭션이 적용되어 있는 것입니다.
TransactionSynchronizationManager.isActualTransactionActive()
트랜잭션 적용 위치
스프링의 @Transactional은 다음 두 가지 규칙이 있습니다.
- 항상 더 구체적이고 자세한 것이 높은 우선순위를 가집니다. 예를 들어, 메서드와 클래스에 애노테이션을 붙일 수 있다면 더 구체적인 메서드가 더 높은 우선순위를 가집니다.
- 클래스에 적용하면 메서드는 자동 적용됩니다. 메서드에 @Transactional이 없다면 더 상위인 클래스를 확인합니다.
다음은 현재 트랜잭션에 적용된 readOnly 옵션의 값을 반환합니다.
TransactionSynchronizationManager.isCurrentTransactionReadOnly()
인터페이스 @Transactional 적용
인터페이스에 @Transactional을 사용하는 것은 스프링 공식 메뉴얼에서 권장하지 않는 방법입니다. AOP를 적용하는 방식에 따라 인터페이스에 애노테이션을 두면 AOP가 적용되지 않는 경우도 있기 때문입니다. 다만 인터페이스에도 @Transactional을 적용할 수 있고, 다음 순서로 적용됩니다.
- 클래스의 메서드 (우선순위가 가장 높다.)
- 클래스의 타입
- 인터페이스의 메서드
- 인터페이스의 타입 (우선순위가 가장 낮다.)
트랜잭션 AOP 주의 사항- 프록시 내부 호출
@Transactional을 적용하면 프록시 객체가 요청을 먼저 받아서 트랜잭션을 처리하고, 실제 객체를 호출해줍니다. 만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 트랜잭션도 적용되지 않습니다.
다음은 테스트 코드의 일부입니다.
@Slf4j static class CallService { public void external() { log.info("call external"); printTxInfo(); this.internal(); } @Transactional public void internal() { log.info("call internal"); printTxInfo(); } private void printTxInfo() { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}", txActive); } }
internal()은 @Transactional을 통해 트랜잭션을 적용합니다. external()은 트랜잭션이 없이 시작하지만, 내부에서 @Transactional이 있는 internal()을 호출합니다. 이 경우 external()은 트랜잭션이 없지만, internal()에서는 트랜잭션이 적용되는 것처럼 보입니다. 그러나 internal()에서 트랜잭션이 전혀 적용되지 않습니다.
실제 호출되는 흐름은 다음과 같습니다.
- 클라이언트가 callService.external()을 호출합니다. 여기서 callService는 트랜잭션 프록시입니다.
- esternal() 메서드에는 @Transactional이 없기 때문에 트랜잭션을 적용하지 않습니다.
- external() 메서드는 내부에서 internal() 메서드를 호출합니다. 자바에서 메서드 앞에 별도의 참조가 없으면 this라는 뜻으로 자기 자신의 인스턴스를 가리킵니다. 결과적으로 자기 자신의 내부 메서드를 호출하는 this.internal()로 실제 대상 객체(target)의 인스턴스를 뜻하며, 이러한 내부 호출은 프록시를 거치지 않습니다.
메서드 내부 호출 때문에 트랜잭션 프록시가 적용되지 않는 문제를 해결하기 위해 internal() 메서드를 별도의 클래스로 분리합니다. CallService에는 트랜잭션 관련 코드가 전혀 없으므로 트랜잭션 프록시가 적용되지 않으며, InternalService에는 트랜잭션 관련 코드가 있으므로 트랜잭션 프록시가 적용됩니다.@Slf4j @RequiredArgsConstructor static class CallService { private final InternalService internalService; public void external() { log.info("call external"); printTxInfo(); internalService.internal(); } private void printTxInfo() { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}", txActive); } } static class InternalService { @Transactional public void internal() { log.info("call internal"); printTxInfo(); } private void printTxInfo() { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}", txActive); } }
Note. 스프링 트랜잭션 AOP 기능은 public 메서드에만 트랜잭션을 적용하도록 기본 설정이 되어 있습니다. public이 아닌 protected, private, package-visible 곳에 @Transactional이 붙어 있으면 예외가 발생하지는 않고, 트랜잭션 적용만 무시됩니다.
트랜잭션 AOP 주의 사항- 초기화 시점
스프링 초기화 시점에는 트랜잭션 AOP가 적용되지 않을 수 있습니다.
@PostConstruct @Transactional public void initV1() { log.info("Hello init @PostConstruct"); }
가장 확실한 대안은 ApplicationReadyEvent 이벤트를 사용하는 것입니다. 해당 이벤트는 스프링 컨테이너가 초기화를 완전히 끝내고 실행 준비가 되었을 때 발생하는 이벤트입니다.
@EventListener(value = ApplicationReadyEvent.class) @Transactional public void init2() { log.info("Hello init ApplicationReadyEvent"); }
[참고 정보]
반응형'BackEnd > Spring DB' 카테고리의 다른 글
트랜잭션 전파(propagation) (0) 2023.02.15 트랜잭션(Transaction) 옵션 (0) 2023.02.11 데이터 접근 기술- 활용 (0) 2023.02.09 데이터 접근 기술- QueryDSL (0) 2023.02.04 데이터 접근 기술- Spring Data JPA (0) 2023.02.04