TroubleShooting - 템플릿 메서드와 트랜잭션 미수행 현상


TroubleShooting

TransactionActions 테스트 진행 중 일종의 버그 발생

  • @Transactional 선언을 했음에도 트랜잭션 반영이 안되는 현상
  • 기대하는 바는 @Transactional 메서드 실행 시 begin, 종료 시 commit or rollback
  • 하지만 각 쿼리 실행 시 begin, commit이 수행됨(debug level 로그 확인)
  • 마치 @Transactional 선언이 안된 것 처럼 수행됨.

위 로그를 살펴보면 총 3 번의 query 실행에 따라 각각 begin, commit을 수행하는 것을 확인할 수 있다.

기대하는 바는 3번의 query가 하나의 트랜잭션으로 묶이고, 트랜잭션 시작 시 begin, 세 번의 쿼리 모두 수행 후 commit을 기대했는데 결과는 그렇지 않다.


원인 - proxy와 템플릿 메서드 패턴

@Transactional이 적용된 구조와 코드를 살펴보면 아래와 같다.


@TransactionalTransactionActions 클래스의 doExecute 메서드에 선언되어 있다.

구조를 보면 알 수 있듯 템플릿 메서드 패턴으로 되어있는데

  • 추상 클래스인 AbstractActionexecute가 외부로부터 호출된다.
  • execute는 구현체의 doExecute를 호출한다.


문제가 되는 곳은 추상 클래스의 메서드가 구현체의 메서드를 호출하는 부분이다.

이는 선언적 트랜잭션의 프록시 기반 Spring AOP 와 관련 이 있는데, 최대한 간단하게 이야기 하자면

스프링은 @Transactional 경계가 설정된 메서드가 외부로부터 호출될 때 target object가 곧바로 호출되지 않고 proxy가 끼어들어 동작 한다.

그리고 트랜잭션과 관련된 기능을 수행하면서 target object의 메서드를 호출한다.


근데 이 proxy가 가로채지 못하는 경우가 생기는데, 주로 내부 메서드에서 @Transactional 선언 메서드 호출이 일어날 때 이다.

즉, 메서드가 클래스 내부에서 호출되면 proxy가 적용되지 않고 결과적으로 @Trnasactional 적용이 되지 않는다.


TransactionsActions 클래스에 적용된 템플릿 메서드 패턴자기 자신의 (@Transactional이 적용된)메서드를 직접 호출하는 꼴이 되어 트랜잭션 적용이 되지 않은 것이다.


이를 해결하기 위한 방법으로 대략 2가지 방법을 제시하는데

  • 외부에서 호출하도록 분리
  • 내부에 멤버로 동일 타입 인스턴스 주입 및 해당 인스턴스로부터 트랜잭션 메서드 호출


위 방법 중 외부에서 호출하도록 분리하는 방법을 택했다.

그 이유는 그나마 스프링에 덜 의존적..이라고 판단을 했다.