반응형
서론
Spring Framework에서 @Transactional
애너테이션은 데이터베이스 트랜잭션 관리를 위한 핵심 기능입니다. 그러나 이 애너테이션이 private 메서드에서 제대로 동작하지 않는 경우가 있습니다. 이는 Spring AOP의 동작 방식과 밀접한 관련이 있습니다. 본 글에서는 Spring AOP의 동작 원리와 @Transactional
이 private 메서드에서 동작하지 않는 이유, 그리고 이를 해결하는 방법에 대해 설명하겠습니다.
Spring AOP의 동작 원리
1. AOP 프록시 생성 방식
Spring AOP는 런타임에 동작하며, JDK Dynamic Proxy와 CGLIB 두 가지 방식으로 프록시를 생성합니다.
JDK Dynamic Proxy
- 인터페이스를 구현한 클래스에 적용
- public 메서드만 AOP 적용 가능
- 인터페이스 기반으로 프록시 생성
CGLIB
- 인터페이스를 구현하지 않은 클래스에 적용
- private을 제외한 모든 메서드에 AOP 적용 가능
- 클래스 상속을 통한 프록시 생성
2. 프록시 생성 시점
@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");
}
}
Self-Invocation 문제
1. 문제 발생 원인
- 같은 클래스 내부에서 메서드 호출 시 프록시를 거치지 않음
- AOP 어드바이스가 적용되지 않음
- 트랜잭션 관리가 제대로 동작하지 않음
2. 테스트 코드 예시
@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);
}
}
해결 방법
1. 자기 자신을 프록시로 주입
@Service
public class SelfInvocation {
private final MemberRepository memberRepository;
private final SelfInvocation selfInvocation; // 자기 자신 주입
public void outerSaveWithPublic(Member member) {
selfInvocation.saveWithPublic(member); // 프록시를 통한 호출
}
}
주의: 순환 의존성 문제가 발생할 수 있어 권장되지 않습니다.
2. 클래스 분리
@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() {
// 내부 로직
}
}
3. AspectJ 사용
- 컴파일 시점에 위빙(Weaving) 수행
- 동일 클래스 내 메서드 호출에도 AOP 적용 가능
- 설정이 복잡할 수 있음
트랜잭션 전파 속성과 Self-Invocation
1. 문제 상황
@Service
public class TransactionService {
@Transactional
public void outer() {
inner(); // REQUIRES_NEW 속성이 무시됨
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 내부 로직
}
}
2. 해결 방법
@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 애플리케이션에서 자주 발생하는 이슈이므로, 미리 대비하는 것이 중요합니다.
참고 자료
반응형
'Backend Development' 카테고리의 다른 글
HTTPS: 웹 보안의 기본, 안전한 통신의 시작 (2) | 2025.05.29 |
---|---|
스택(Stack) 자료구조: 개념부터 구현까지 (4) | 2025.05.29 |
MySQL InnoDB의 락킹 메커니즘: 갭락과 넥스트키 락 이해하기 (0) | 2025.05.29 |
CORS 이해하기: 크로스 오리진 리소스 공유의 모든 것 (0) | 2025.05.29 |
네트워크 타임아웃 이해하기: Connection, Socket, Read Timeout (2) | 2025.05.29 |