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이 정상적으로 적용된다.
또한 역할이 분리되어 코드의 책임도 더 명확해진다.
'BackEnd > Spring' 카테고리의 다른 글
| [Spring] @PathVariable vs @RequestParam 차이와 사용 기준 (0) | 2026.08.07 |
|---|---|
| [Spring] 왜 Exception을 던질까? (0) | 2026.08.07 |
| [Spring] JPA 메서드 명명 규칙(Query Method) (0) | 2026.08.07 |
| [Spring] Proxy란? @Transactional과 @Lazy가 동작하는 원리 (0) | 2026.08.06 |
| [Spring] Bean 심화 - @Bean, @Qualifier, @Order 정리 (0) | 2026.08.06 |
댓글