개요
앞선 포스팅에서 @Transactional 애노테이션 적용 시 프록시 방식의 AOP를 통해 트랜잭션이 적용되는 내용을 정리했습니다.
https://jaimemin.tistory.com/2271
이번 게시글에서는 @Transactional 적용 시 주의할 사항을 정리해 보겠습니다.
@Transactional 적용 시 주의 사항
@Transactional을 적용 시 주의할 점이 크게 세 가지가 있습니다.
- 트랜잭션을 적용하기 위해서는 항상 프록시를 통해 호출 필요
- @Transactional은 public 메서드에만 적용 가능
- @Transactional은 초기화 시점 이후부터 적용 가능
1. 트랜잭션을 적용하기 위해서는 항상 프록시를 통해 호출 필요
@Transactional 애노테이션은 "트랜잭션을 적용하겠다"라고 메서드나 서비스에 선언하기만 하면 프록시 방식의 AOP를 통해 트랜잭션이 적용되는 방식으로 편리하게 트랜잭션을 적용할 수 있습니다.
즉, 트랜잭션을 적용하기 위해서는 항상 프록시를 통해 타겟 객체를 호출해야 합니다.
달리 말하자면 프록시를 거치지 않고 대상 객체를 직접 호출하게 될 경우 @Transactional 애노테이션을 적용한 개발자의 기대와 달리 AOP가 적용되지 않고 트랜잭션도 적용되지가 않습니다.
코드로 예시를 들어보겠습니다.
위 테스트 코드를 보면 하나의 서비스 내 트랜잭션이 적용되지 않은 nonTransactionalFunc() 메서드가 있고 @Transactional이 적용된 transactionalFunc() 메서드가 하나의 서비스 내 존재하는 것을 확인할 수 있습니다.
여기서 핵심은 트랜잭션이 적용되지 않은 nonTransactionalFunc() 메서드 내에서 @Transactional이 적용된 transactionalFunc() 메서드를 호출한다는 점입니다.
transactionalFunc() 메서드를 호출하면 우리가 예상할 수 있듯이 아래와 같이 transaction이 적용된 것을 확인할 수 있습니다.
그러면 nonTransactionalFunc()를 호출한다면?
대부분 저처럼 nonTransactionalFunc()에는 트랜잭션이 적용되지 않지만 내부에서 transactionalFunc() 메서드를 호출하면 트랜잭션이 적용되어 있을 것이라고 예상했을 것입니다.
하지만 예상과 달리 아래와 같이 둘 다 트랜잭션이 적용되지 않은 것을 확인할 수 있습니다.
이는 nonTransactionalFunc() 내부에서 transactionalFunc() 메서드를 AOP를 통해 호출하는 것이 아니라 직접 호출하기 때문입니다.
메서드 내 this가 생략되어 있지만 사실상 nonTransactionalFunc() 메서드 내 this.transactionalFunc() 코드가 실행되면서 직접 호출했기 때문에 트랜잭션이 적용되지 않은 것을 확인할 수 있습니다.
위 문제에 대한 대처 방법
그렇다면 위 문제를 어떻게 해결해야 할까?
가장 간단한 방법은 아래와 같이 트랜잭션이 적용된 메서드를 별도 클래스로 분리하는 것입니다.
해당 코드에서는 nonTransactionService의 nonTransactionalFunc()에서 transactionalFunc()를 직접 호출하는 것이 아니라 별도 클래스의 프록시가 적용된 transactionalFunc() 메서드를 호출하는 것을 확인할 수 있습니다.
따라서, 저희가 기존에 예상했듯이 nonTransactionalFunc()에서는 트랜잭션이 적용되지 않고 transactionalFunc() 메서드가 호출되었을 때는 트랜잭션이 적용된 것을 확인할 수 있습니다.
2. @Transactional은 public 메서드에만 적용 가능
스프링 트랜잭션 AOP 기능은 public 메서드에서만 트랜잭션을 적용할 수 있도록 기본 설정이 되어있습니다.
이는 트랜잭션이 주로 비즈니스 로직의 시작점에 걸기 때문에 대부분 외부에 열어준 곳을 시작점으로 사용하기 때문입니다.
public이 아닌 protected, private 메서드에 선언한다고 해서 exception이 터지지는 않지만 예상과 달리 트랜잭션이 적용되지 않는 문제점이 발생합니다,
3. @Transactional은 초기화 시점 이후부터 적용 가능
스프링 프레임워크에서는 초기화 코드가 실행된 뒤 트랜잭션 AOP가 적용됩니다.
따라서, @PostConstruct 애노테이션과 @Transactional 애노테이션을 같이 사용할 경우 예상과 달리 트랜잭션이 적용되지 않는 것을 확인할 수 있습니다.
초기화 시점 이후에 바로 트랜잭션이 적용돼야 하는 상황이라면 트랜잭션 AOP를 포함한 스프링 컨테이너가 완전히 생성되고 난 후 이벤트가 붙은 메서드를 호출하면 됩니다.
예를 들자면 아래와 같이 ApplicationReadyEvent가 발생되는 시점에 @Transactional을 선언하면 트랜잭션이 잘 적용되는 것을 확인할 수 있습니다.
참고
스프링 DB 2편 - 데이터 접근 핵심 원리 (김영한 강사님)
'Spring' 카테고리의 다른 글
[Spring Batch] 스프링 배치 도메인 정리 (0) | 2023.07.06 |
---|---|
[Spring] 트랜잭션 전파 정리 (0) | 2023.04.30 |
@Transactional 동작 원리 (0) | 2023.03.29 |
Springboot 동작 방식 및 분석하는 방법 (0) | 2023.03.15 |
[Spring] @Conditional 정리 (2) | 2023.03.07 |