[Spring] Transactional 사용 시 자기 호출(Self-Invocation) 이슈

2025. 9. 11. 00:07·Spring

Self-Invocation

Self-invocation (자기 호출)은 같은 클래스 안에서, 자신의 메서드를 직접 호출하는 경우 발생하는 문제이다. 스프링에서 `@Transactional` , `@Async` , AOP 기반의 기능은 프록시를 통해 동작을 한다. 그런데, `this.someMethod()` 처럼 내부 호출을 하면 프록시를 거치지 않고 실제 객체의 메서드가 실행된다. 따라서 프록시가 개입하지 못하니 AOP 기능이 적용되지 않는다. 

외부에서 호출할 때 : 클라이언트 -> 프록시 -> 실제 메서드 실행
내부에서 자기호출할 때 : `this.innerMethod()` ->  바로 실제 메서드 실행 (프록시를 거치지 않음)

 

이게 왜 문제인거냐 ? 

개발자는 `@Transactional` , `@Async` , `@Cacheable` 등을 메서드에 붙이면 항상 적용될 것이라고 기대한다. 그런데 내부에서 자기 메서드를 호출하면 프록시가 안 타서 어노테이션이 무시된다. 즉, 코드와 실행 결과가 달라져서 버그로 이어지기 쉽다. 


예시 

@Service
public class OrderService {

    public void outerMethod() {
        // 같은 클래스 내부에서 innerMethod 호출
        // self-invocation (프록시 안탐)
        innerMethod();
    }

    @Transactional
    public void innerMethod() {
        // DB 저장 로직
        throw new RuntimeException("예외 발생");
    }
}
  • 기대 : `innerMethod` 가 트랜잭션을 시작해서, 예외 발생 시 rollback 될 것 
  • 실제 : `outerMethod()` → `innerMethod()` 호출 시 프록시를 안 거침 → 트랜잭션 시작 안 됨 → rollback 안 됨 → 데이터가 그대로 db에 저장됨 → 큰 문제 발생 

해결 방법 

1) 메서드를 다른 빈으로 분리한다. 

AOP는 프록시를 통과해야 켜진다. 같은 클래스 안에서 호출하면 프록시를 거치지 않기 때문에 안 먹히는 상황이다. 그래서 프록시를 무조건 거치도록 메서드를 다른 클래스(빈)으로 떼어내는 방법이 가장 깔끔하다. 

@Service
@RequiredArgsConstructor
public class OrderFacade {
    private final OrderService orderService;

    public void placeOrder() {
        // 트랜잭션 안 걸린 작업들
        // 다른 빈 호출 → 프록시 통과 → @Transactional 적용
        orderService.saveOrder();
    }
}

@Service
public class OrderService {
    @Transactional
    public void saveOrder() {
        // DB 저장
        throw new RuntimeExpetion("예외 발생");
    }
}

이렇게 하면 `saveOrder()` 는 반드시 프록시를 거쳐 호출되기 때문에 `@Transactional` 이 100% 동작한다. 이 방식이 가장 많이 권장되는 방식이다. 

 

2) 자기 자신(프록시)을 주입

스프링이 관리하는 '나 자신'의 프록시 객체를 직접 불러서 내부 호출도 프록시를 거치도록 만드는 방법이다. 

@Service
@RequiredArgsConstructor
public class UserService {
    private final @Lazy UserService self; // 자기 자신 프록시 주입 (@Lazy로 순환 방지)

    public void signup() {
        // 프록시를 거쳐서 호출 → @Transactional 동작함
        self.createUser();
    }

    @Transactional
    public void createUser() {
        // 여기서는 트랜잭션이 제대로 시작됨
    }
}

`self.createUser()` 는 단순히 `this.createUser()` 가 아니라 스프링이 대신 만들어둔 프록시를 거친다. 그 덕분에 트랜잭션이 적용된다. 그러나 단점이라면 `@Lazy`를 붙여야 순환참조 문제가 안 생기고, 코드만 보면 왜 self 를 쓰는지 이해가 어려울 수 있다. 그래서 빠르게 해결해야 할 때만 주로 사용한다. 

'Spring' 카테고리의 다른 글

[Spring] 객체지향 쿼리 언어  (0) 2025.10.02
[Spring] 즉시 로딩과 지연 로딩  (0) 2025.09.21
[Spring] Spring Events 사용해 이벤트 발행하기  (0) 2025.09.07
[Spring] Setter 사용을 지양해야하는 이유  (0) 2025.08.29
[Spring] DTO class를 record로 사용하는 이유  (0) 2025.08.29
'Spring' 카테고리의 다른 글
  • [Spring] 객체지향 쿼리 언어
  • [Spring] 즉시 로딩과 지연 로딩
  • [Spring] Spring Events 사용해 이벤트 발행하기
  • [Spring] Setter 사용을 지양해야하는 이유
erika0915
erika0915
백엔드 개발자가 되고 싶어요 .
  • erika0915
    erikoding
    erika0915
  • 전체
    오늘
    어제
    • 분류 전체보기 (78)
      • 프로젝트 (13)
        • 끼니콩 (3)
        • 덕메랑 (3)
        • handDoc (7)
        • Haeil (0)
      • Java (9)
        • 클린코더스 (0)
      • Spring (30)
      • Redis (3)
      • CS (7)
        • 운영체제 (3)
        • 컴퓨터구조 (0)
        • 네트워크 (4)
      • DevOps (2)
      • 코딩테스트 (0)
      • Tech (14)
        • TDD (1)
        • 정리 (5)
        • 우테코 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Spring
    스프링부트
    CoolSMS
    레디스
    스프링
    promtail
    STT
    Network
    파인튜닝
    몽고디비
    깃
    운영체제
    github
    coderabbit
    MongoDB
    깃허브
    코드레빗
    도커
    TDD
    springboot
    자바
    docker
    OS
    지라
    git
    java
    jira
    redis
    AI
    네트워크
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
erika0915
[Spring] Transactional 사용 시 자기 호출(Self-Invocation) 이슈

티스토리툴바