IT/Spring

@Transactional이 작동하지 않는 진짜 이유

eddie_factory 2025. 5. 24. 21:16
반응형

Spring에서 트랜잭션 처리를 할 때 가장 많이 사용하는 애노테이션이 @Transactional입니다.
하지만 분명히 @Transactional을 붙였는데도 롤백이 되지 않거나, 트랜잭션이 작동하지 않는 현상을 겪는 경우가 많습니다.

이 글에서는 그런 상황들이 왜 발생하는지, 그리고 어떻게 해결할 수 있는지 실제 사례 중심으로 살펴봅니다.

✅ 기본 개념 정리

@Transactional은 Spring AOP 기반으로 작동합니다.
즉, 프록시 객체를 통해 메서드 호출을 가로채서 트랜잭션을 시작하거나 커밋/롤백을 결정합니다.

❌ 트랜잭션이 작동하지 않는 흔한 원인

1. 자기 자신 호출 (self-invocation)

@Service
public class UserService {

    @Transactional
    public void outerMethod() {
        innerMethod(); // ❌ 트랜잭션 적용 안 됨
    }

    public void innerMethod() {
        // 작업 수행
    }
}

 

같은 클래스 안에서 @Transactional이 붙은 메서드를 호출하면 프록시를 거치지 않기 때문에 트랜잭션이 적용되지 않습니다.

 

해결 방법

  • 내부 호출을 분리하여 다른 빈에서 호출하거나
  • ApplicationContext를 주입받아 자기 자신을 프록시로 호출합니다.

2. 인터페이스 기반 프록시에서 클래스 메서드 직접 호출

Spring은 기본적으로 JDK 동적 프록시를 사용하며, 이는 인터페이스만 감쌉니다.
따라서 구현 클래스에서 트랜잭션이 있는 메서드를 직접 호출하면 프록시를 우회하게 됩니다.

해결 방법

  • 클래스 기반 프록시(proxyTargetClass = true)를 사용하거나
  • @EnableTransactionManagement(proxyTargetClass = true) 설정을 확인합니다.

3. 예외가 롤백되지 않는 경우

@Transactional
public void process() {
    throw new CustomException(); // ❌ 롤백되지 않음
}

기본적으로 @TransactionalRuntimeException(비체크 예외)만 롤백 대상입니다.
Exception이나 커스텀 체크 예외는 롤백되지 않습니다.

해결 방법

  • rollbackFor 속성을 설정하여 명시적으로 롤백 대상을 지정합니다.
@Transactional(rollbackFor = Exception.class)

 


4. 비동기나 스케줄러에서 트랜잭션 무시

@Async, @Scheduled 등은 별도의 스레드에서 실행되므로, 트랜잭션 전파가 되지 않습니다.

해결 방법

  • 트랜잭션이 필요한 로직은 별도의 서비스로 분리하여 해당 빈을 통해 호출합니다.

결론

@Transactional은 매우 강력한 도구지만, AOP 기반이라는 구조적 특성을 이해하지 못하면 작동하지 않는 경우를 자주 겪게 됩니다.

실무에서는 다음 3가지를 꼭 기억해야 합니다.

  1. 프록시 우회 여부 (자기 호출, 내부 호출 등)
  2. 예외가 롤백 대상인지 여부
  3. 트랜잭션 경계 설정이 명확한지

기본 원리를 이해하고 사용하면, @Transactional은 개발 생산성과 안정성을 모두 확보할 수 있는 강력한 무기가 됩니다.

 

반응형