# Transactional을 바탕으로 한 스프링 AOP의 동작 방식
## 1. 스프링 AOP와 @Transactional
- 스프링 AOP(Aspect-Oriented Programming)는 관점 지향 프로그래밍을 통해 부가적인 로직(로깅, 트랜잭션 관리, 보안 등)을 비즈니스 로직과 분리하여 재사용성을 높이고 코드 중복을 줄인다. @Transactional은 AOP를 기반으로 하여 선언적 트랜잭션 관리를 제공한다.
- @Transactional은 메서드나 클래스 레벨에서 선언되고, 런타임 시 스프링이 트랜잭션 경계를 관리한다. 내부적으로 스프링은 프록시 패턴을 사용해 트랜잭션 관련 부가기능을 실행한다
## 2. 프록시 패턴과 동작 원리
### 2.1 프록시의 생성
- 스프링 컨테이너는 @Transactional이 선언된 클래스나 메서드를 감싸는 프록시 객체를 생성한다. 이 프록시는 실제 객체 대신 클라이언트에 반환되며, 메서드 호출을 가로채 트랜잭션 관련 로직을 처리한다.
- JDK 동적 프록시: 인터페이스가 있는 경우 사용
- CGLIB 프록시: 클래스 기반 프록시로, 인터페이스가 없는 경우 사용
### 2.2 프록시 동작 과정
1. 클라이언트가 @Transactional이 붙은 메서드를 호출
2. 프록시 객체는 호출을 가로채 트랜잭션 매니저(TransactionManager)에게 제어를 위임
3. 트랜잭션 매니저는 트랜잭션 시작, 커밋, 롤백 등의 처리를 수행
4. 트랜잭션이 관리되는 동안 실제 비즈니스 메서드를 실행
5. 메서드 실행이 완료되면 트랜잭션 매니저는 트랜잭션 상태에 따라 커밋하거나 롤백
## 3. 스레드 로컬(ThreadLocal)을 통한 트랜잭션 관리
스프링의 트랜잭션은 스레드 로컬(ThreadLocal)을 사용해 각 스레드마다 독립적인 트랜잭션 컨텍스트를 제공한다.
### 3.1 ThreadLocal 역할
- 각 스레드에서 고유한 트랜잭션 객체를 유지할 수 있도록 한다
- TransactionSynchronizationManager가 ThreadLocal에 트랜잭션 상태를 저장한다
- 동시성 문제가 발생하지 않도록 트랜잭션 경계를 스레드 단위로 분리한다
### 3.2 ThreadLocal 동작 과정
1. 트랜잭션 시작 시 TransactionSynchronizationManager는 현재 스레드의 컨텍스트에 트랜잭션 정보를 저장한다
2. 이후의 데이터베이스 작업은 이 트랜잭션 정보에 따라 수행된다.
3. 트랜잭션이 종료되면 ThreadLocal의 트랜잭션 정보가 제거된다.
## 4. 내부 참조(Self-Invocation) 이슈
### 4.1 문제 설명
- @Transactional이 선언된 메서드가 같은 클래스의 내부 메서드에서 호출되는 경우, 프록시 객체를 경유하지 않아 트랜잭션이 적용되지 않을 수 있다
- 프록시는 외부에서의 호출만 가로챌 수 있다
- 내부 호출은 프록시를 거치지 않고 실제 객체를 바로 호출하므로 트랜잭션 관련 로직이 실행되지 않는다
### 4.2 해결 방안
1. 구조 변경: 내부 호출을 외부로 분리하여 프록시를 거치도록 설계합니다.
- e.g. 호출하는 메서드를 별도의 빈으로 분리.
- AOP 적용 변경: AOP 적용 방식을 직접 호출하는 방식에서 비침투적으로 변경.
- 3. Programmatic Transaction API: 내부 호출에서도 트랜잭션을 적용하기 위해 프로그래밍 방식으로 트랜잭션을 처리한다
```java
@Service
public class ExampleService {
@Transactional
public void outerMethod() {
innerMethod(); // 트랜잭션 적용되지 않음
}
@Transactional
public void innerMethod() {
// 트랜잭션 필요 작업
}
}
// 해결 방안: 내부 메서드 호출 분리
@Service
public class ExampleService {
private final ExampleService selfProxy;
public ExampleService(ExampleService selfProxy) {
this.selfProxy = selfProxy;
}
@Transactional
public void outerMethod() {
selfProxy.innerMethod(); // 프록시를 통해 호출
}
@Transactional
public void innerMethod() {
// 트랜잭션 필요 작업
}
}
```
## 5. 정리 및 결론
- 프록시 패턴: @Transactional은 프록시 패턴을 통해 메서드 호출을 가로채 트랜잭션 관리를 수행한다
- ThreadLocal: 스레드별 독립적인 트랜잭션 컨텍스트를 제공하여 동시성 문제를 방지한다
- 내부 호출 이슈: 내부 호출 시 프록시를 우회하여 트랜잭션이 적용되지 않을 수 있으며, 이를 해결하기 위한 설계 변경이 필요하다
스프링의 트랜잭션 관리는 선언적 접근 방식과 AOP를 결합하여 개발자 경험을 향상시키는 동시에 안정적인 데이터베이스 작업을 지원한다. 이를 적절히 이해하고 활용하면 더욱 효율적이고 안전한 애플리케이션을 설계할 수 있다!