
Spring Framework에서 @Transactional 애너테이션은 데이터베이스 트랜잭션 관리를 위한 핵심 기능입니다. 그러나 이 애너테이션이 private 메서드에서 제대로 동작하지 않는 경우가 있습니다. 이는 Spring AOP의 동작 방식과 밀접한 관련이 있습니다. 본 글에서는 Spring AOP의 동작 원리와 @Transactional이 private 메서드에서 동작하지 않는 이유, 그리고 이를 해결하는 방법에 대해 설명하겠습니다.
Spring AOP는 런타임에 동작하며, JDK Dynamic Proxy와 CGLIB 두 가지 방식으로 프록시를 생성합니다.
@Slf4j
@RequiredArgsConstructor
@Service
public class SelfInvocation {
private final MemberRepository memberRepository;
@Transactional
public void saveWithPublic(Member member) {
log.info("call saveWithPublic");
memberRepository.save(member);
throw new RuntimeException("rollback test");
}
@Transactional
private void saveWithPrivate(Member member) {
log.info("call saveWithPrivate");
memberRepository.save(member);
throw new RuntimeException("rollback test");
}
}
@SpringBootTest
class SelfInvocationTest {
@Autowired
private SelfInvocation selfInvocation;
@Test
void outerSaveWithPublic() {
Member member = new Member("test");
try {
selfInvocation.outerSaveWithPublic(member);
} catch (RuntimeException e) {
log.info("catch exception");
}
List<Member> members = memberRepository.findAll();
// 트랜잭션이 정상 동작하지 않아 롤백되지 않음
assertThat(members).hasSize(1);
}
}
@Service
public class SelfInvocation {
private final MemberRepository memberRepository;
private final SelfInvocation selfInvocation; // 자기 자신 주입
public void outerSaveWithPublic(Member member) {
selfInvocation.saveWithPublic(member); // 프록시를 통한 호출
}
}
주의: 순환 의존성 문제가 발생할 수 있어 권장되지 않습니다.
@Service
public class OuterTransactionService {
private final InnerTransactionService innerTransactionService;
@Transactional
public void outer() {
innerTransactionService.inner();
}
}
@Service
public class InnerTransactionService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 내부 로직
}
}
@Service
public class TransactionService {
@Transactional
public void outer() {
inner(); // REQUIRES_NEW 속성이 무시됨
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 내부 로직
}
}
@Service
public class OuterTransactionService {
private final InnerTransactionService innerTransactionService;
@Transactional
public void outer() {
innerTransactionService.inner(); // 별도 클래스로 분리
}
}
@Service
public class InnerTransactionService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 내부 로직
}
}
Spring AOP는 프록시 기반으로 동작하기 때문에, 같은 클래스 내부에서의 메서드 호출에는 AOP 어드바이스가 적용되지 않습니다. 이를 해결하기 위해서는 클래스를 분리하거나 AspectJ를 사용하는 것이 좋습니다.
인사이트: Spring AOP의 동작 방식을 이해하면 트랜잭션 관리뿐만 아니라 다른 AOP 기반 기능들도 더 효과적으로 활용할 수 있습니다. 특히 Self-Invocation 문제는 Spring 애플리케이션에서 자주 발생하는 이슈이므로, 미리 대비하는 것이 중요합니다.