Spring AOP
Spring AOP는 프록시 패턴으로 트랜잭션 제어 코드를 비즈니스 로직에 동적으로 주입한다. @Transactional이 선언된 빈이 컨테이너에 등록될 때, Spring은 실제 객체를 감싸는 프록시 객체를 자동으로 만들어 등록한다.[1] 이 프록시가 Spring Transaction의 선언적 트랜잭션이 서 있는 토대이며,[1:1] Transaction Propagation의 전파 속성이 이 프록시 경계 안에서 적용된다고 연결하는 것은 이 page의 해석이다.
프록시가 트랜잭션을 여는 흐름
- 클라이언트가 메서드를 호출하면 프록시 객체의 메서드가 대신 먼저 호출되며, 내부의
PlatformTransactionManager를 통해 DB 트랜잭션을 시작(begin)한다.[2] - 트랜잭션이 열린 상태에서 실제 Target 객체의 비즈니스 로직 메서드를 대리 실행한다.[2:1]
- 메서드가 정상 종료되면 트랜잭션을 커밋하고, 예외가 발생하면 예외 종류에 따라 롤백 또는 커밋을 수행한다.[2:2]
sequenceDiagram
autonumber
actor Client as Client (Controller 등)
participant Proxy as AOP Proxy
(트랜잭션 경계)
participant TxManager as PlatformTransactionManager
participant Target as Target Service
(비즈니스 로직)
participant DB as DB
Note over Client, Proxy: 외부 호출일 때만 Proxy 작동
Client->>Proxy: 메서드 호출
Proxy->>TxManager: 트랜잭션 시작 (begin)
TxManager->>DB: SET autocommit = false
Proxy->>Target: 실제 타겟 메서드 위임
alt 정상 종료
Target-->>Proxy: 리턴
Proxy->>TxManager: Commit
TxManager->>DB: COMMIT
else Unchecked 예외
Target-->>Proxy: Throw RuntimeException
Proxy->>TxManager: Rollback
TxManager->>DB: ROLLBACK
end
Proxy-->>Client: 결과 반환내부 호출(Self-Invocation)에서 트랜잭션이 사라지는 이유
- 프록시 방식의 특성상, 외부 객체가 프록시를 통해 메서드를 호출할 때만 AOP 트랜잭션이 작동한다.[3]
- 클래스 내부에서
@Transactional이 없는 메서드가 동일 클래스 내의 다른@Transactional메서드를this.method()로 직접 호출하면, 프록시를 거치지 않고 실제 객체의 레퍼런스를 직접 참조하므로 트랜잭션이 적용되지 않는다.[3:1] - 해결책: 트랜잭션이 필요한 비즈니스 단위를 별도의 Service Bean으로 분리(Facade 패턴 등은 이 page가 덧붙인 예시)해 항상 외부 호출(프록시 경유)이 일어나도록 설계하는 것이 가장 권장된다. Self-Injection을 쓸 수도 있으나 구조적 분리가 우선이다.[4]
관련
- Spring Transaction — 이 프록시 구조를 기반으로 전파 속성, 롤백 규칙, 격리 수준이 적용된다.
- Transaction Propagation — 프록시가 여는 트랜잭션 경계 안에서 다음 메서드 호출이 합류·분리되는 정책.
- Audit Log — 로그를 독립 트랜잭션으로 남길 때 REQUIRES_NEW로 별도 프록시 경계를 연다.
출처
테스트 질문
- Spring이
@Transactional을 어떻게 비즈니스 로직에 주입하는가? - 왜 내부 호출(self-invocation)에서 트랜잭션이 사라지는가?
- Self-Invocation 문제를 해결하는 가장 권장되는 방법은 무엇인가?
Transaction.md — "스프링의 선언적 트랜잭션은 **Spring AOP(관점 지향 프로그래밍)**를 바탕으로 동작합니다. 구체적으로는 프록시 패턴을 사용하여 트랜잭션 제어 코드를 비즈니스 로직에 동적으로 주입합니다."(L44), "@Transactional이 선언된 빈(Bean)이 컨테이너에 등록될 때, 스프링은 실제 객체를 감싸는 프록시 객체(Proxy)를 자동으로 만들어 등록합니다."(L47) ↩︎ ↩︎
Transaction.md — "클라이언트가 메서드를 호출하면 프록시 객체의 메서드가 대신 먼저 호출되며, 내부의 PlatformTransactionManager를 통해 DB 트랜잭션을 시작(begin)합니다.", "트랜잭션이 열린 상태에서 실제 Target 객체의 비즈니스 로직 메서드를 실행합니다.", "메서드가 정상 종료되면 트랜잭션을 커밋(Commit)합니다." ↩︎ ↩︎ ↩︎
Transaction.md — "프록시 방식의 특성상, 외부 객체가 프록시를 통해 메서드를 호출할 때만 AOP 트랜잭션이 작동합니다.", "클래스 내부에서 @Transactional이 없는 메서드가 동일 클래스 내의 다른 @Transactional 메서드를 직접 호출(this 호출)하는 경우, 프록시를 거치지 않고 실제 객체의 레퍼런스를 직접 참조하므로 트랜잭션이 적용되지 않습니다." ↩︎ ↩︎
Transaction.md — "가장 권장되는 방법은 트랜잭션이 필요한 비즈니스 단위를 별도의 클래스(서비스 빈)로 분리하여 외부 호출 구조로 변경하는 것입니다.", "스프링의 의존성 셀프 주입(Self-Injection)을 활용하여 내부 호출을 회피합니다." ↩︎