이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


트랜잭션 AOP(Aspect-Oriented Programming)는 스프링 프레임워크가 트랜잭션 관리를 위해 제공하는 선언적 트랜잭션 관리 방식이다. 이 방법을 통해 개발자는 비즈니스 로직과 트랜잭션 관리 코드를 분리할 수 있으며, 이로 인해 코드의 가독성과 유지보수성이 크게 향상된다. 스프링에서는 @Transactional 어노테이션을 사용하여 클래스나 메소드 레벨에서 트랜잭션을 선언적으로 관리할 수 있게 한다.

 

트랜잭션 AOP의 특징은 아래와 같다

  • @Transactional 어노테이션 사용: 개발자는 트랜잭션을 적용하고자 하는 메소드나 클래스에 @Transactional 어노테이션을 붙인다. 이 어노테이션은 스프링에게 해당 메소드나 클래스의 실행을 트랜잭션 경계로 관리하도록 지시한다.
  • 프록시 기반의 AOP: 스프링은 @Transactional 어노테이션이 붙은 대상 객체를 대신하여 프록시 객체를 생성한다. 이 프록시 객체는 실제 객체의 메소드 호출을 가로채 트랜잭션을 시작하고, 메소드 실행이 성공적으로 완료되면 트랜잭션을 커밋하거나, 예외가 발생하면 롤백한다.
  • 트랜잭션 매니저 연동: 스프링의 트랜잭션 AOP는 PlatformTransactionManager 인터페이스 구현체와 연동하여 트랜잭션을 관리한다. JDBC, Hibernate, JPA 등 다양한 데이터 액세스 기술에 맞는 트랜잭션 매니저를 설정할 수 있다.

 

트랜잭션 AOP를 사용함으로써 개발자는 복잡한 트랜잭션 관리 로직을 작성할 필요 없이, 비즈니스 로직에만 집중할 수 있다. 또한, @Transactional 어노테이션을 통해 선언적으로 트랜잭션 관리를 할 수 있어, 코드의 가독성과 유지보수성이 크게 향상된다.

 

프록시를 도입하지 않으면 서비스 계층의 로직에서 트랜잭션을 직접 시작한다.

public void accountTransfer(String fromId, String toId, int money) throws SQLException {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); //트랜잭션 시작
    try {
        bizLogic(fromId, toId, money); //비즈니스 로직
        transactionManager.commit(status); //성공시 커밋
        } catch (Exception e) {
        transactionManager.rollback(status); //실패시 롤백
        throw new IllegalStateException(e);
    }
}

 

프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있다.

@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
    bizLogic(fromId, toId, money);
}
  • 프록시 도입 전: 서비스에 비즈니스 로직과 트랜잭션 처리 로직이 함께 섞여있다.
  • 프록시 도입 후: 트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다. 그리고 트랜잭션을 시작한 후에 실제 서 비스를 대신 호출한다. 트랜잭션 프록시 덕분에 서비스 계층에는 순수한 비즈니즈 로직만 남길 수 있다.

 

@Transactional 애노테이션은 메서드에 붙여도 되고, 클래스에 붙여도 된다. 클래스에 붙이면 외부에서 호출 가능한 public 메서드가 AOP 적용 대상이 된다.

 

프록시의 간단한 원리

스프링 AOP는 프록시 패턴을 기반으로 한다. 이는 스프링 컨테이너가 @Transactional 어노테이션이 붙은 클래스의 객체를 생성할 때, 원본 객체 대신 트랜잭션 로직을 추가로 갖는 프록시 객체를 생성하고, 이 프록시 객체가 원본 객체를 감싸는 구조이다. 이 프록시 객체는 원본 객체의 모든 메소드 호출을 가로채 트랜잭션 관리 기능을 수행한다.

 

트랜잭션 AOP를 사용할 때, 트랜잭션 매니저 관련 메소드들은 자동으로 실행된다. 이는 스프링 프레임워크가 @Transactional 어노테이션을 분석하고, 해당 어노테이션이 붙은 메소드를 실행할 때 자동으로 트랜잭션 경계를 설정하기 때문이다. 사용자는 복잡한 트랜잭션 관리 로직을 직접 작성할 필요 없이, 선언적으로 트랜잭션 관리를 할 수 있다.

  1. 트랜잭션 시작: @Transactional이 붙은 메소드 호출 시, 스프링 AOP는 먼저 PlatformTransactionManager를 사용하여 트랜잭션을 시작한다. 이 과정에서 getTransaction() 메소드가 내부적으로 호출되며, 필요한 트랜잭션 설정(전파 방식, 격리 수준 등)을 적용한다.
  2. 비즈니스 로직 실행: 트랜잭션이 시작된 후, 실제 비즈니스 로직이 있는 메소드의 내용이 실행된다.
  3. 트랜잭션 커밋 또는 롤백: 비즈니스 로직의 실행이 성공적으로 마무리되면, 스프링 AOP는 PlatformTransactionManager를 통해 트랜잭션을 커밋한다. 이때 commit() 메소드가 호출된다. 만약 실행 도중 예외가 발생하면, 스프링은 rollback() 메소드를 호출하여 트랜잭션을 롤백한다.

 

트랜잭션 매니저 관련 메소드들이 자동으로 실행될 때, 데이터 소스는 스프링의 의존성 주입(Dependency Injection, DI) 기능을 통해 자동으로 주입된다. 

 

트랜잭션 프록시 코드 예시

public class TransactionProxy {
private MemberService target;

public void logic() { //트랜잭션 시작

    TransactionStatus status = transactionManager.getTransaction(..);
    try {
    	target.logic();//실제 대상 호출
    	transactionManager.commit(status); //성공시 커밋
    } catch (Exception e) { 
    	transactionManager.rollback(status); //실패시 롤백
    	throw new IllegalStateException(e);
		}
    }
}
public class Service {
     public void logic() {
        bizLogic(fromId, toId, money);//트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
    }
}

 

참고

스프링 AOP를 적용하려면 어드바이저, 포인트컷, 어드바이스가 필요하다. 스프링은 트랜잭션 AOP 처리를 위해 다음 클래스를 제공한다. 스프링 부트를 사용하면 해당 빈들은 스프링 컨테이너에 자동으로 등록된다.
어드바이저: BeanFactoryTransactionAttributeSourceAdvisor
포인트컷: TransactionAttributeSourcePointcut
어드바이스: TransactionInterceptor