본문 바로가기
  • Let's study
BackEnd/Spring

[Spring] @Transactional이 내부 호출에서 동작하지 않는 이유

by 코딩고수이고파 2026. 8. 7.

Spring에서 @Transactional을 붙였는데 트랜잭션이 적용되지 않는 경우가 있다.

대표적인 경우가 같은 객체 내부에서 메서드를 호출하는 경우이다.

문제 상황

예를 들어 아래와 같은 코드가 있다고 하자.

 

@Service
public class PersonService {

    public void work() {
        save(); // 내부 호출
    }

    @Transactional
    public void save() {
        System.out.println("저장");
    }
}

 

work()를 호출하면 save()에도 @Transactional이 있으므로 트랜잭션이 시작될 것 같지만, 실제로는 트랜잭션이 적용되지 않는다.

왜 동작하지 않을까?

Spring의 @Transactional은 Proxy를 통해 동작한다.

외부에서 Bean을 호출하면 실제 객체가 아니라 Proxy가 먼저 호출된다.

 

사용자
   ↓
Proxy
   ↓
PersonService

 

Proxy가 호출되면서

 

  • 트랜잭션 시작
  • 실제 메서드 실행
  • 커밋 또는 롤백

을 자동으로 처리한다.

내부 호출은 Proxy를 거치지 않는다

문제는 같은 객체 내부에서 메서드를 호출하면 Proxy를 거치지 않는다는 것이다.

 

public void work() {
    save();
}
사용자
   ↓
Proxy
   ↓
work()
   ↓
save()

 

work()까지는 Proxy를 거치지만,

save()는 객체 내부에서 직접 호출되므로 Proxy가 개입하지 못한다.

결과적으로 @Transactional이 동작하지 않는다.

Proxy를 거치도록 호출하기

이 문제는 자기 자신의 Proxy를 주입받아 해결할 수 있다.

 

@Autowired
@Lazy
private AppConfig self;

 

그리고 Proxy를 통해 메서드를 호출한다.

 

self.save();

 

이렇게 하면 실행 순서는 다음과 같다.

 

사용자
   ↓
Proxy
   ↓
work()
   ↓
Proxy
   ↓
save()

 

save()도 Proxy를 거치게 되어 @Transactional이 정상적으로 동작한다.

@Lazy를 사용하는 이유

자기 자신을 주입하면 순환 참조가 발생할 수 있다.

 

PersonService
   ↓
PersonService

 

@Lazy를 사용하면 객체 생성을 실제 사용하는 시점까지 미루기 때문에 순환 참조 문제를 피할 수 있다.

 

자기 자신의 Proxy를 주입하는 방법도 가능하지만, 실무에서는 권장되지 않는다.

대부분은 트랜잭션이 필요한 기능을 별도의 Service로 분리한다.

 

PersonService
        ↓
PersonTransactionService

 

@Service
public class PersonService {

    private final PersonTransactionService transactionService;

    public void work() {
        transactionService.save();
    }
}

 

이렇게 하면 다른 Bean을 호출하게 되므로 항상 Proxy를 거쳐 @Transactional이 정상적으로 적용된다.

또한 역할이 분리되어 코드의 책임도 더 명확해진다.

댓글