패키지:
org.springframework.transaction.aspectj
@Transactional의 기본 동작은 프록시다. 그러나 프록시 방식은 같은 클래스 안의 자기 호출(this.save())에 트랜잭션이 걸리지 않고, 비 public 메서드나 final 클래스/메서드에는 적용이 까다롭다. 도메인 객체나 레거시 코드에서 이런 제약이 문제가 될 때, AspectJ 모드가 답이 된다.
이 패키지는 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)를 켰을 때 동작하는 트랜잭션 애스펙트를 제공한다. 위빙 방식이므로 호출 경로와 무관하게, 모든 가시성의 메서드에 트랜잭션 경계가 적용된다.
TransactionAspectSupport (spring-tx)
└─ invokeWithinTransaction(method, targetClass, callback)
: 트랜잭션 시작 → 콜백 실행 → 커밋/롤백 — "진짜 로직"
▲ extends
AbstractTransactionAspect.aj
├─ around(txObject) : transactionalMethodExecution(txObject)
│ → invokeWithinTransaction(...) 어댑터
└─ abstract pointcut transactionalMethodExecution(Object)
▲ extends ▲ extends
AnnotationTransactionAspect.aj JtaAnnotationTransactionAspect.aj
├─ AnnotationTransactionAttribute ├─ @RequiredTypes(jakarta...Transactional)
│ Source (Spring @Transactional) ├─ jakarta.transaction.Transactional 매칭
└─ pointcut = public-in-@Tx-type └─ 동일 패턴, JTA 표준 애너테이션
|| @Tx-method
[활성화]
AspectJTransactionManagementConfiguration
└─ @Bean transactionAspect = AnnotationTransactionAspect.aspectOf()
AspectJJtaTransactionManagementConfiguration (extends 위)
└─ @Bean jtaTransactionAspect = JtaAnnotationTransactionAspect.aspectOf()
AbstractTransactionAspect:TransactionAspectSupport를 상속해around어드바이스 하나로 그 로직에 연결한다. 어떤 메서드가 트랜잭션 대상인지는 추상 pointcut에 위임.AnnotationTransactionAspect: Spring의@Transactional(org.springframework.transaction.annotation)을 매칭하는 구체 애스펙트.JtaAnnotationTransactionAspect: JTA 표준jakarta.transaction.Transactional을 매칭. 클래스패스에 해당 애너테이션이 있을 때만 위빙되도록@RequiredTypes로 보호.
public class OrderService { // 인터페이스 없어도, 프록시 없어도 됨
@Transactional
public void place(Order o) { ... }
public void doWork() {
place(o); // ← self-invocation. 프록시 모드면 트랜잭션 누락. 위빙이면 OK.
}
}service.place(o) (또는 this.place(o) 자기 호출)
│
│ AspectJ가 place() 메서드 바이트코드에 위빙해 둔 around 진입
▼
AbstractTransactionAspect.around(txObject)
│ pointcut: transactionalMethodExecution(txObject)
│ = (@Transactional 타입의 public 메서드 || @Transactional 메서드) && this(txObject)
│
▼ thisJoinPoint.getSignature() → MethodSignature → Method 추출
▼
invokeWithinTransaction(method, txObject.getClass(), callback)
│ (TransactionAspectSupport — spring-tx의 진짜 로직)
│ ├─ TransactionAttributeSource로 @Transactional 속성 해석(전파/격리/롤백규칙)
│ ├─ 트랜잭션 시작
│ ├─ callback.proceedWithInvocation() ──▶ proceed(txObject) // 원래 메서드 본문
│ ├─ 정상 종료 → commit
│ └─ 예외 → rollback 규칙에 따라 rollback
▼
결과 반환 (또는 예외 재던지기)
around 어드바이스의 본질은 어댑터다. AspectJ의 조인 포인트 정보(thisJoinPoint)를 spring-tx가 이해하는 Method + 콜백 형태로 변환해 invokeWithinTransaction에 넘기는 것뿐이다.
// AbstractTransactionAspect (발췌)
Object around(final Object txObject): transactionalMethodExecution(txObject) {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
try {
return invokeWithinTransaction(methodSignature.getMethod(), txObject.getClass(),
new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return proceed(txObject); // 원래 메서드 실행
}
});
}
catch (RuntimeException | Error ex) { throw ex; }
catch (Throwable thr) { Rethrower.rethrow(thr); throw new IllegalStateException(...); }
}
// AnnotationTransactionAspect
executionOfAnyPublicMethodInAtTransactionalType :
execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *)
→ 클래스에 @Transactional이 붙으면, 그 타입(및 서브타입)의 모든 public 메서드
executionOfTransactionalMethod :
execution(@Transactional * *(..))
→ 메서드에 직접 @Transactional이 붙으면 가시성 불문(비 public도)
클래스 레벨 애너테이션은 public 메서드의 기본 트랜잭션 의미를 정하고, 메서드 레벨 애너테이션은 가시성과 무관하게 개별 적용·재정의한다. 비 public 메서드에 트랜잭션을 거는 유일한 방법이 메서드에 직접 애너테이션을 다는 것이라는 점이 javadoc에 명시돼 있다.
destroy()→clearTransactionManagerCache(): 애스펙트는 사실상 싱글톤이므로, 컨테이너 종료 시 캐시된TransactionManager매핑을 비운다.DisposableBean구현으로 라이프사이클에 연결.Rethrower.rethrow(Throwable): AspectJaround는 명시적으로 선언된 checked 예외만 던질 수 있다. 그러나 위빙 대상 메서드는 임의의 checked 예외를 던질 수 있으므로, 제네릭 타입 소거를 악용해 원래 예외를 그대로 다시 던진다. "추하지만 안전한 우회"라고 주석이 인정한다.aspectOf()빈 등록:AspectJTransactionManagementConfiguration이 위버 싱글톤을 꺼내TransactionAttributeSource와TransactionManager(있으면)를 주입한다.
- 프록시 모드와 동일 의미: 트랜잭션 시작/커밋/롤백·전파·격리 로직은 전부
TransactionAspectSupport에 있고, 프록시용TransactionInterceptor와 이 애스펙트가 그것을 공유한다. 모드를 바꿔도@Transactional의 의미는 같다. - 인터페이스 애너테이션 미상속 주의: javadoc이 강조하듯, AspectJ는 자바 규칙을 따라 인터페이스의 애너테이션을 상속하지 않는다. 따라서
@Transactional은 인터페이스가 아니라 구현 클래스(또는 그 메서드)에 달아야 한다. 이는 인터페이스 프록시도 지원하는 프록시 모드와의 차이점이다. - JTA 분리와 조건부 위빙:
JtaAnnotationTransactionAspect는@RequiredTypes("jakarta.transaction.Transactional")로, 해당 애너테이션이 클래스패스에 없으면 위빙 자체가 일어나지 않게 한다.AspectJJtaTransactionManagementConfiguration이 Spring·JTA 두 애스펙트를 함께 등록한다.
트랜잭션 애스펙트는 @Transactional을 프록시 없이 바이트코드 위빙으로 적용해, self-invocation·비 public 메서드·final 타입의 제약을 없앤다. AbstractTransactionAspect의 around가 조인 포인트를 Method+콜백으로 변환해 TransactionAspectSupport.invokeWithinTransaction에 넘기는 얇은 어댑터이고, 실제 트랜잭션 로직은 spring-tx와 공유한다. Spring용·JTA용 두 구체 애스펙트가 각자 pointcut만 다르게 제공하며, 애너테이션은 반드시 구현 클래스에 달아야 한다는 점이 프록시 모드와의 핵심 차이다.