Spring AOP와 @Transactional: private 메서드에서의 동작 방식

2025. 5. 29. 00:37·Backend Development
반응형

서론

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 애플리케이션에서 자주 발생하는 이슈이므로, 미리 대비하는 것이 중요합니다.


참고 자료

  • Spring Framework Documentation - AOP
  • Spring Framework Documentation - Transactions
  • AspectJ Documentation
반응형
저작자표시 비영리 변경금지 (새창열림)

'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
'Backend Development' 카테고리의 다른 글
  • 스택(Stack) 자료구조: 개념부터 구현까지
  • MySQL InnoDB의 락킹 메커니즘: 갭락과 넥스트키 락 이해하기
  • CORS 이해하기: 크로스 오리진 리소스 공유의 모든 것
  • 네트워크 타임아웃 이해하기: Connection, Socket, Read Timeout
Kun Woo Kim
Kun Woo Kim
안녕하세요, 김건우입니다! 웹과 앱 개발에 열정적인 전문가로, React, TypeScript, Next.js, Node.js, Express, Flutter 등을 활용한 프로젝트를 다룹니다. 제 블로그에서는 개발 여정, 기술 분석, 실용적 코딩 팁을 공유합니다. 창의적인 솔루션을 실제로 적용하는 과정의 통찰도 나눌 예정이니, 궁금한 점이나 상담은 언제든 환영합니다.
  • Kun Woo Kim
    WhiteMouseDev
    김건우
  • 깃허브
    포트폴리오
    velog
  • 전체
    오늘
    어제
  • 공지사항

    • [인사말] 이제 티스토리에서도 만나요! WhiteMouse⋯
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기 (100) N
      • Frontend Development (39) N
      • Backend Development (21) N
      • Algorithm (33) N
        • 백준 (11) N
        • 프로그래머스 (17)
        • 알고리즘 (5)
      • Infra (1)
      • 자료구조 (3)
  • 링크

    • Github
    • Portfolio
    • Velog
  • 인기 글

  • 태그

    frontend development
    tailwindcss
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Kun Woo Kim
Spring AOP와 @Transactional: private 메서드에서의 동작 방식
상단으로

티스토리툴바