DHistory

[Spring] Self-Invocation? 본문

Programming/Spring Boot

[Spring] Self-Invocation?

DHistory 2026. 6. 1. 07:49

Question

Spring @Transactional이 같은 클래스 내 메서드 호출 시 트랜잭션이 적용되지 않는 이유는 무엇인가요?

이를 해결하는 방법과 각 방법의 트레이드 오프는 무엇인가요?

 

 

Answer 1) @Transactional 동작 과정

> @Transactional은 Spring AOP 프록시 기반으로 동작

> @Transactional은 Proxy 객체 메서드 진입 시 시작

> 같은 클래스 내 this.method()는 Proxy 객체가 아닌 원본 객체를 직접 호출

> this.method() 에 선언된 @Transactional 동작 무시

> 코드 작성 의도와 런타임 시 코드 수행 동작 차이 발생

 

class PaymentService {

    @Transactional
    public void processOrder() {
        // 트랜잭션이 분리되지 않고 processOrder 트랜잭션과 함께 처리
        this.pay()
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void pay() { ... }
}

 

 

Answer 2) 외부 클래스로 분리

> 외부 클래스로 분리하여 Proxy 객체 호출

> SRP 원칙을 준수하고 명확하고 테스트 용이

> But. 클래스 증가

 

Answer 3) AspectJ Weaving 으로 해결 가능 여부

> AspectJ 로 해결이 가능, Bytecode 조작으로 성능 향상 이점 (AOP 적용 시 Reflection, Interceptor Chain 사용)

> But. 빌드 복잡도, 디버깅, 오류 발견 시점 지연 (컴파일 -> 런타임), 팀 러닝커브 증가 문제

 

Answer 4) Event Publishing으로 Transaction 경계 분리

> DDD, Hexagonal Architecture에서 Application Service Layer 단위가 하나의 Transaction 단위가 되고 내부 도메인 서비스와 분리

> 레이어 경계를 따르면 자연스러운 분리

> But. 내부 이벤트 유실 가능성

  > Transactional Outbox Pattern 도입으로 해결 가능

 

  @EventListener @TransactionalEventListener
시점 이벤트 발행 즉시 트랜잭션 커밋 후
트랜잭션 관계 발행자 트랜잭션에 편승 독립적
롤백 시 같이 롤백 실행 X
용도 같은 트랜잭션에 묶고 싶을 때 커밋 확정 후 후처리

 

 

ETC.

Self Injection @Lazy 기반

> 의도 불명확

 

class PaymentService(
    @Lazy private val self: PaymentService
) {

    fun processOrder() {
        self.pay()
    }
    
    @Transactional
    fun pay() { ... }
}

 

Application Context

> Spring 강결합, 테스트 복잡도 증가

> 의도 불명확, 가독성 저하

class PaymentSerivce(
    private val applicationContext: ApplicationContext
) {

    fun processOrder() {
         val self = applicationConext.getBean(PaymentService:class.java)
         self.pay()
    }
    
    @Transactional
    fun pay() { ... }
}

 

AopProxy.currentProxy()

> AOP 프레임워크 강결합

> 의도 불명확, 가독성 저하

 

class PaymentService {

    fun processOrder() {
        (AopContext.currentProxy() as PaymentService).pay()
    }
    
    @Transactional
    fun pay() { ... }
}