01.들어가기 전
self-invocation 즉, 자기호출 트랜잭션 관련 설정에서 발생할 수 있는 문제인데용
항상... 프로젝트를 하면서 느끼지만 잘 모르고 쓰는것들이 많고 그 안을 파고들어야 더 깊이 공부할 수 있는 것 같아요
그리고 그걸 남겨야 ^^..... 머리속에 오래 남더라구요? 그래서 이번에는 self-invocation이 발생할 수 있는 상황을 만들어보고, 테스트 하는 과정을 살펴보자
그전에 먼저 인터페이스를 만들고 간단한 예제를 만들어보자
public interface Business {
void ready();
void go();
}
@Slf4j
public class SimpleBusiness implements Business {
@Override
public void ready() {
log.info("------ready");
go(); // this.go()
}
@Override
public void go() {
log.info("------go");
}
}
- ready()에서 go()를 요청하는 모습
- 이때, go 는 this.go(); 인 셈
아래는 프록시를 통해 호출하기 위해서 만들어둔 클래스 입니다!
@Slf4j
public class ExecuteLoggingAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("####### execute method [{}]", invocation.getMethod().getName());
return invocation.proceed();
}
}
- ExecuteLoggingAdvice
- MethodInterceptor 인터페이스를 구현하는 Advice 클래스
- invoke 메서드: 메서드 호출 일어날때 마다 실행, 부가작업 추가 가능
- 메서드 호출 전 로그를 남기고, 그 다음 타겟 객체의 원본 메서드를 실행
- SimpleBusiness 클래스를 프록시 대상으로 설정 , 전달하여 기본 타겟 객체를 설정
02. 직접 ready() 호출하기 테스트
@Test
@DisplayName("business를 만들어서 직접 호출한다")
void direct_business() {
final Business bs = new SimpleBusiness();
bs.ready();
}
- 만들어서 ready()를 호출
- 결과

- ready → go 이 호출됨
03.프록시 이용해 ready() 호출 테스트
@Test
@DisplayName("proxy를 이용해서 호출한다")
void proxy_self_invocation() {
final ProxyFactory factory = new ProxyFactory(new SimpleBusiness());
factory.addInterface(Business.class);
factory.addAdvice(new ExecuteLoggingAdvice());
final Business bs = (Business) factory.getProxy();
bs.ready();
}
- 코드 설명
- ProxyFactory: 스프링에서 제공하는 프록시 생성 도구
- 인터페이스(Business)와 대상객체(SimpleBusiness)를 지정하여 객체의 대리역할을 하는 프록시 객체 생성
- 프록시에 부가 작업(advice - ExecuteLoggingAdvice) 추가
bs.ready()호출하면
-> 이때 프록시 객체는 SimpleBusiness객체에 대한 모든 메서드 호출을 가로채서 ExecuteLoggingAdvice의 invoke메서드 호출
- 결과

- execute method [go]가 호출이 안되었네요
04. 어떻게 동작하는걸까?
client → ready() 호출 → proxy → go()호출 → target(SimpleBusiness)에 도달
- target object에 도달하면, 자기자신 호출이 가능
- 이때, this.ready(), this.go()
- proxy가 아닌 this 참조로 호출한 것 → go()는 내부호출!
- execute method [go]가 호출되지 않은 이유
⇒ 이걸 self-invocation이라고 하는데,,,
transcational에서 이러한 self-invocation이 발생할 수 있음
한마디로 말하자면 객체 내 메서드가 동일 객체의 다른 메서드를 호출할 때 발생한다~
-> 스프링에서는 AOP, 트랜잭션 설정시 주로 나타날수 있는 문제이다!

05. self-invocation
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public void doSomething(final Member member) {
saveMember(member); //self-invocation 발생
}
@Transactional
public void saveMember(final Member member) {
memberRepository.save(member);
}
}
→ 근데 intellij에서 saveMember(member); 요기에 노란줄 그어준다~
@Transactional self-invocation (in effect, a method within the target object calling another method of the target object)
does not lead to an actual transaction at runtime
- doSomething → saveMember()를 호출
- 사실 Member 저장이되는데, 이건 transcational이 동작해서 그런것이 아닌, this로 저장된 것
- AOP 기능이 수행되지 않아 트랜잭션 기능이 동작하지 않았다고 할 수 있음
→ 해결하려면?
service 단에 @Transactional 붙이거나, doSomething 위에 @Transactional 붙이면 된다!