반응형

Spring @Transactional이 내부 메소드 호출에서 동작하지 않는 이유

Spring Boot에서 @Transactional을 사용할 때 한 가지 흔히 겪는 함정이 있습니다.
바로, 같은 클래스 내에서 메소드를 호출하면 트랜잭션이 적용되지 않는다는 점입니다.

이번 포스팅에서는 이 현상이 왜 발생하는지, 그리고 어떻게 해결할 수 있는지 실제 예제를 통해 자세히 알아보겠습니다.


🧪 상황 예제: 트랜잭션이 안 먹는다?

@Service
public class AService {

    @Transactional
    public void methodA() {
        methodB(); // 내부 호출
    }

    public void methodB() {
        // 예외 발생
        throw new RuntimeException("예외 발생!");
    }
}
 

이 코드를 보면, methodA()는 @Transactional이 붙어 있어 트랜잭션이 시작되고,
그 안에서 호출되는 methodB()에서 예외가 발생하니 당연히 롤백될 것 같죠?

하지만 실제 실행해보면...
👉 롤백되지 않고 커밋됩니다!


🤔 왜 이런 일이 벌어질까?

이유는 Spring이 @Transactional을 프록시 기반 AOP로 구현하고 있기 때문입니다.

Spring의 트랜잭션 처리 흐름은 이렇게 됩니다:

  1. Spring이 @Transactional이 붙은 클래스를 감싸는 프록시 객체를 만듭니다.
  2. 외부에서 해당 메소드를 호출하면 프록시가 감지하여 트랜잭션을 시작합니다.
  3. 하지만, 같은 클래스 안에서 메소드를 호출하면 프록시를 거치지 않고 this.method() 형태로 직접 호출하게 됩니다.
  4. 따라서 트랜잭션을 적용할 기회를 놓쳐버립니다!

📌 즉, 프록시가 개입해야 트랜잭션이 적용되는데, 내부 호출은 프록시를 생략하므로 적용되지 않음!


💡 쉽게 말해서?

마치 건물 입구에 감시카메라(프록시)가 달려 있다고 생각해보세요.

  • 외부에서 들어오면 감시카메라가 감지해서 트랜잭션을 시작함
  • 하지만 내부에서 옆방으로 이동하면 감시카메라가 그걸 감지 못함
    → 트랜잭션 안 걸림

🛠 해결 방법

✅ 방법 1. 메소드를 다른 클래스로 분리

@Service 
public class BService { 

	@Transactional 
	public void methodB() { 
		throw new RuntimeException("예외 발생!"); 
	} 
    
}
@Service
public class AService {

    private final BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }

    public void methodA() {
        bService.methodB(); // 외부 호출 → 프록시 적용됨!
    }
}

✅ 방법 2. ApplicationContext를 통해 프록시 자신을 가져와 호출

@Service
public class AService {

    private final ApplicationContext context;

    public AService(ApplicationContext context) {
        this.context = context;
    }

    public void methodA() {
        AService proxy = context.getBean(AService.class);
        proxy.methodB(); // 프록시를 통한 호출
    }

    @Transactional
    public void methodB() {
        throw new RuntimeException("예외 발생!");
    }
}

✅ 결론

@Transactional은 프록시를 거쳐야만 동작한다!
내부에서 자기 메소드를 호출하면 프록시를 거치지 않으므로 트랜잭션이 동작하지 않는다.

 

같은 클래스 내 메소드 간 호출에는 트랜잭션이 적용되지 않는다는 점,
항상 기억하고 설계하시길 바랍니다.


🔁 정리

구분트랜잭션 적용됨?설명
외부에서 AService.methodA() 호출 ✅ 적용됨 프록시가 감지함
AService.methodA() → 내부에서 methodB() 호출 ❌ 적용 안 됨 프록시를 거치지 않음
AService → BService.methodB() 호출 ✅ 적용됨 외부 클래스 프록시 통과
AService → context.getBean(AService.class).methodB() 호출 ✅ 적용됨 프록시를 수동으로 거침

+ Recent posts