
오늘의 목표
스프링에서는 어떻게
@Transactional하나로 트랜잭션이 적용될 수 있는지 전반적인 과정에 대해 알아보겠습니다.
자동구성..근데-이제-Data-JPA를-곁들인 을 통해 Data JPA를 사용하게 되었을 때 어떠한 자동구성이 일어나게 되는지를 확인하면서 마지막에 TransactionAutoConfiguration을 잠깐 다루었습니다.
TransactionAutoConfiguration을 통해 트랜잭션 관리를 위한 AOP 설정 및 인프라가 구성이 되는데, 해당 글은 TransactionAutoConfiguration에서부터 시작해 스프링에서 어떻게 @Transactional 하나로 트랜잭션이 적용될 수 있는지 전반적인 과정에 대해 알아보겠습니다.

TransactionAutoConfiguration의 내부 코드를 자세히 살펴보면 아래와 같이 구성되어 있습니다.
// 트랜잭션 자동 구성 클래스
@AutoConfiguration // Spring Boot의 자동 구성 클래스임을 선언
@ConditionalOnClass(PlatformTransactionManager.class) // Classpath에 트랜잭션 관련 클래스가 있을 경우에만 활성화
public class TransactionAutoConfiguration {
/**
* 리액티브 트랜잭션 처리용 빈 등록
* - ReactiveTransactionManager가 하나만 있을 경우, TransactionalOperator 빈을 자동 등록
*/
@Bean
@ConditionalOnMissingBean // 이미 동일한 타입의 빈이 없는 경우에만 등록
@ConditionalOnSingleCandidate(ReactiveTransactionManager.class) // 후보가 하나일 경우에만 등록
public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
return TransactionalOperator.create(transactionManager);
}
/**
* 일반 트랜잭션을 위한 TransactionTemplate 구성
* - PlatformTransactionManager가 단일 후보로 있을 경우 적용
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {
/**
* TransactionOperations 빈이 없는 경우 TransactionTemplate 등록
* - 명시적 트랜잭션 처리 시 사용됨
*/
@Bean
@ConditionalOnMissingBean(TransactionOperations.class)
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
/**
* 트랜잭션 AOP (프록시 기반 @Transactional) 활성화 구성
* - 트랜잭션 매니저가 있고, 수동으로 @EnableTransactionManagement를 안 붙였을 경우에만 적용
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TransactionManager.class) // 트랜잭션 매니저가 있을 때만 적용
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class) // 개발자가 수동 설정하지 않았을 경우
public static class EnableTransactionManagementConfiguration {
/**
* JDK 동적 프록시 방식 (@EnableTransactionManagement(proxyTargetClass = false))
* - 인터페이스 기반 프록시
* - spring.aop.proxy-target-class=false일 경우 적용
*/
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
public static class JdkDynamicAutoProxyConfiguration {
// 설정용 빈만 존재, 실제 로직은 없음
}
/**
* CGLIB 기반 클래스 프록시 (@EnableTransactionManagement(proxyTargetClass = true))
* - 클래스 기반 프록시
* - 기본값 (matchIfMissing = true)
*/
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
// 설정용 빈만 존재, 실제 로직은 없음
}
}
/**
* AspectJ 트랜잭션 처리 지원
* - @Transactional을 AspectJ 방식으로 사용하는 경우
* - AbstractTransactionAspect 빈이 존재해야 적용됨
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(AbstractTransactionAspect.class)
static class AspectJTransactionManagementConfiguration {
/**
* AbstractTransactionAspect는 지연 초기화 대상에서 제외해야 함 (즉시 초기화 필요)
*/
@Bean
static LazyInitializationExcludeFilter eagerTransactionAspect() {
return LazyInitializationExcludeFilter.forBeanTypes(AbstractTransactionAspect.class);
}
}
}
TransactionAutoConfiguration은 Spring Boot에서 @EnableTransactionManagement를 자동으로 등록해주는 역할을 합니다.
즉, 스프링 부트 환경이 아니면 개발자가 직접 @EnableTransactionManagement를 붙여야 합니다.
TransactionAutoConfiguration은 자동 등록의 트리거일 뿐
여기서 주의깊게 보아야할 부분은 TransactionTemplateConfiguration 과 EnableTransactionManagementConfiguration 입니다.

TransactionTemplate은 Spring에서 명시적 프로그래밍 방식으로 트랜잭션을 관리할 수 있게 해주는 템플릿 클래스입니다. 보통 @Transactional은 선언적 방식인데, 이건 프로그래밍 방식으로 트랜잭션을 제어하고자 할 때 사용합니다.
// 사용 예시
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
String result = txTemplate.execute(status -> {
// 트랜잭션 안에서 실행할 코드
someRepository.save(...);
return "성공";
});
초기에는 트랜잭션을 사용하기 위해
PlatformTransactionManager를 직접 호출해 트랜잭션을 시작하고, 성공 시 커밋하거나 예외 발생 시 롤백하는 코드를 작성해야 했습니다. 이로 인해 동일한 트랜잭션 제어 로직이 여러 클래스에서 반복적으로 사용되는 문제가 발생했습니다.
이를 해결하기 위해 TransactionTemplate이 도입되었습니다. TransactionTemplate을 사용하면 템플릿 콜백 패턴을 통해 트랜잭션 처리 코드를 일관되게 작성할 수 있고, 트랜잭션 시작, 커밋, 롤백 로직을 템플릿이 대신 처리해줍니다. 이 덕분에 반복되는 트랜잭션 제어 코드를 제거할 수 있게 되었습니다.
하지만 여전히 문제는 남아 있었습니다. TransactionTemplate을 사용하는 방식에서도 비즈니스 로직과 트랜잭션 처리 로직이 같은 클래스 안에 섞여 있어 두 관심사를 하나의 클래스에서 처리하게 되므로 관심사 분리가 어렵고 유지보수가 불편하다는 점입니다.
이 문제를 해결하기 위해 스프링은 AOP 기반의 선언적 트랜잭션 처리 방식을 제공합니다. @Transactional 어노테이션을 사용하면, 트랜잭션 경계 설정을 AOP 프록시가 대신 처리해주기 때문에, 개발자는 오직 비즈니스 로직에만 집중할 수 있습니다. 트랜잭션의 시작과 종료, 롤백 여부 등은 AOP 프레임워크가 자동으로 처리합니다.
맞습니다. @Transactional 을 사용하게 된다면 AOP 기반의 트랜잭션 처리를 이용하는 것으로 TransactionTemplate은 전혀 등장하지 않습니다.
대신 TransactionInterceptor 가 메서드 호출을 가로채서 기존에 TransactionTemplate이 수행했던 트랜잭션 시작, 커밋, 롤백 로직을 대신 수행해주게 됩니다.

해당 클래스는 Spring Boot에서 @EnableTransactionManagement를 자동 설정하는 자동 구성 클래스입니다. 구체적으로는, AOP 기반 트랜잭션 처리에서 JDK 동적 프록시(JDK Proxy)와 CGLIB 프록시 중 어떤 것을 사용할지 자동으로 설정해주는 역할을 합니다.
@EnableTransactionManagement는 Spring에서 @Transactional이 실제로 동작하도록 활성화해주는 어노테이션입니다. -> 트랜잭션 AOP 설정을 활성화하는 역할 (환경 세팅)
@EnableTransactionManagement 없으면 @Transactional은 무시됨 (프록시가 안 만들어짐)@EnableTransactionManagement만 있고 @Transactional이 없으면? → 아무 효과 없음둘 다 있어야 AOP 기반 트랜잭션 처리가 정상적으로 작동
이 어노테이션이 적용되면 Spring은 TransactionManagementConfigurationSelector를 통해 다음 두 Bean을 등록합니다
AutoProxyRegistrar: AOP 프록시 인프라 구성ProxyTransactionManagementConfiguration: Transaction Advisor와 Interceptor 등록이 클래스는 InfrastructureAdvisorAutoProxyCreator를 빈으로 등록합니다. 이 빈(InfrastructureAdvisorAutoProxyCreator)은 @Transactional이 붙은 메서드를 자동으로 프록시로 감싸줍니다 (즉, 동작을 가로채기 위한 프록시 생성)
즉, AutoProxyRegistrar 클래스는 @EnableTransactionManagement, @EnableAsync, @EnableAspectJAutoProxy 등
AOP 기반 기능을 활성화하는 어노테이션들에 의해 등록되는 클래스입니다.
이 클래스의 역할은 AOP 기능을 수행할 AutoProxyCreator 빈을 등록하는 것입니다.
즉, 실제 AOP를 수행하는 객체는 아니고, 프록시를 생성해줄 객체(AutoProxyCreator)를 스프링 컨테이너에 등록해주는 역할을 합니다.
내부에서는..
@EnableTransactionManagement → ImportSelector로 AutoProxyRegistrar를 importAutoProxyRegistrar.registerBeanDefinitions()에서 InfrastructureAdvisorAutoProxyCreator(=AutoProxyCreator) 같은 프록시 생성기 빈을 BeanDefinitionRegistry에 등록 (아래 사진 참고)이후 Spring이 빈을 생성할 때 이 AutoProxyCreator가 개입해서 프록시 객체(AOP 대상)를 감싸도록 합니다.
그럼 진짜 프록시를 만드는 주체는?
AutoProxyRegistrar가 아니라 AutoProxyCreator들이 실제 프록시를 생성합니다.대표적인 AutoProxyCreator
InfrastructureAdvisorAutoProxyCreator : @Transactional, @Async 등 인프라 수준의 AOP용AnnotationAwareAspectJAutoProxyCreator : @Aspect 기반 AOP용BeanNameAutoProxyCreator : 빈 이름으로 지정된 AOP 대상 프록시 생성
AnnotationAwareAspectJAutoProxyCreator는 @Aspect 기반 AOP를 처리하기 위한 빈 후처리기 (BeanPostProcessor).
그리고 이건 우리가 흔히 말하는 커스텀 AOP (예: 로깅, 인증 체크, 성능 측정 등) 에서 동작하는 핵심 컴포넌트.

결국은 AutoProxyRegistrar을 통해 AOP 프록시 생성기가 생성된다는 것을 알았습니다.
정리하자면
AutoProxyRegistrar 클래스는 InfrastructureAdvisorAutoProxyCreator를 빈으로 등록 InfrastructureAdvisorAutoProxyCreator 은 @Transactional이 붙은 메서드를 자동으로 프록시로 감싸줌ProxyTransactionManagementConfiguration는 트랜잭션을 위한 Advisor 및 Interceptor 등록하는 역할을 수행합니다.

TransactionInterceptor (트랜잭션 로직을 수행)TransactionAttributeSourceAdvisor (Advisor이며 Pointcut + Advice 형태)최종적으로 @EnableTransactionManagement에 의해 등록된 AutoProxyRegistrar와 ProxyTransactionManagementConfiguration을 통해 @Transactional이 붙은 메서드는 프록시로 감싸지게 됩니다.
이 프록시는 내부적으로 TransactionInterceptor를 호출합니다.
TransactionInterceptor의 invoke 메서드를 호출하게 되는데,

TransactionInterceptor는 상속받은 TransactionAspectSupport의 invokeWithinTransaction() 메서드를 통해 트랜잭션을 시작하고, 커밋하거나 롤백하는 과정을 수행합니다.

Spring에서 @Transactional이 붙은 메서드가 실행될 때, 트랜잭션이 실제로 필요한 경우 트랜잭션을 생성하고 정보 객체를 구성하는 핵심 메서드가 바로 TransactionAspectSupport의 createTransactionIfNecessary()입니다.
이 메서드는 트랜잭션 처리 흐름의 진입부인 invokeWithinTransaction() 내부에서 호출됩니다.
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
if (txAttr != null && ((TransactionAttribute)txAttr).getName() == null) {
txAttr = new DelegatingTransactionAttribute((TransactionAttribute)txAttr) {
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction((TransactionDefinition)txAttr);
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
}
}
return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);
}
큰 흐름을 정리하면 아래와 같습니다.
@Transactional)의 큰 흐름
@EnableTransactionManagement (자동 적용됨)
↓
TransactionManagementConfigurationSelector
↓
┌────────────────────────┬────────────────────────────┐
│ AutoProxyRegistrar │ ProxyTransactionConfig │
│ → AOP Creator 등록 │ → TransactionInterceptor │
│ │ → TransactionAdvisor │
└────────────────────────┴────────────────────────────┘
↓
@MyService 등록됨 → 이때 @Transactional 프록시로 감쌈
↓
MyService.method() 호출 → 프록시의 invoke()
↓
TransactionInterceptor → invokeWithinTransaction()
↓
PlatformTransactionManager → getTransaction() / commit() / rollback()
이렇게 해서 TransactionAutoConfiguration 부터 시작해서 TransactionInterceptor까지의 과정을 통해 스프링 내부에서 선언적 트랜잭션이 적용되는 전반적인 과정을 알 수 있었습니다. 다음에는 PlatformTransactionManager의 동작 방식에 대해 알아보겠습니다.
아래는 자동구성..근데-이제-Data-JPA를-곁들인 부터 시작해서 지금까지의 흐름을 정리한 표입니다.
| 단계 | 설명 | 관련 클래스 또는 어노테이션 | 생명주기 타이밍 |
|---|---|---|---|
| 0️⃣ | 애플리케이션 시작, SpringApplication.run() 호출 | SpringApplication | 애플리케이션 부트 |
| 1️⃣ | 사용자 정의 @Component, @Service 등의 스캔 시작 | @ComponentScan, ConfigurationClassPostProcessor | 빈 정의 등록 시작 전 |
| 2️⃣ | 사용자 정의 빈 구성 클래스(@Configuration 등) 먼저 처리됨 | @Configuration, @Bean | 등록 우선순위 ↑ |
| 3️⃣ | @EnableAutoConfiguration에 따라 AutoConfig 클래스 로딩 | spring.factories → META-INF | 자동 구성 클래스 탐색 시점 |
| 4️⃣ | DataSourceAutoConfiguration 실행 → HikariDataSource 등록 | DataSourceAutoConfiguration | DB 연결 구성 |
| 5️⃣ | HibernateJpaAutoConfiguration 실행 | JpaVendorAdapter, EntityManagerFactoryBuilder | DataSource 이후 |
| → | HibernateJpaConfiguration 로드 | LocalContainerEntityManagerFactoryBean | |
| → | JpaTransactionManager, EntityManagerFactory 등록 | JpaBaseConfiguration 상속 | |
| 6️⃣ | JpaRepositoriesAutoConfiguration 실행 | Repository 스캔 | Hibernate 이후 |
| 7️⃣ | TransactionAutoConfiguration 실행 | 트랜잭션 관련 설정 | DataSource, JPA 이후 |
| → | EnableTransactionManagementConfiguration 내부에서 | ||
| → | @EnableTransactionManagement 자동 적용 | 중요 트리거 | |
| → | TransactionManagementConfigurationSelector → Import 두 개 | ||
| → | AutoProxyRegistrar → InfrastructureAdvisorAutoProxyCreator 등록 | AOP 프록시 생성기 | |
| → | ProxyTransactionManagementConfiguration → TransactionInterceptor, TransactionAdvisor 등록 | 어드바이저 구성 완료 | |
| 8️⃣ | 이 시점에 @Service, @Component 등 사용자 정의 빈 등록 시작 | 일반적인 빈 생성 시점 | |
| 9️⃣ | 등록된 BeanPostProcessor 동작 시작 | InfrastructureAdvisorAutoProxyCreator 등 | |
| → | Advisor 조건 만족 시 프록시 감싸짐 (@Transactional 등) | ✅ 프록시 객체 생성 | |
| 🔟 | 사용자 코드에서 메서드 호출 시 → 프록시 객체가 가로채기 | ||
| → | TransactionInterceptor.invoke() 실행됨 | ||
| → | 내부적으로 invokeWithinTransaction() 호출 | 트랜잭션 시작, 커밋, 롤백 처리 | |
| → | PlatformTransactionManager.getTransaction() 호출 | 트랜잭션 생성 여부 판단 | |
| → | invocation.proceedWithInvocation() | 실제 비즈니스 로직 실행 | |
| → | 정상 종료 시 commitTransactionAfterReturning() 실행 | → PlatformTransactionManager.commit() 호출 | |
| → | 예외 발생 시 completeTransactionAfterThrowing() 실행 | → PlatformTransactionManager.rollback() 호출 |
| 라이프사이클 단계 | 설명 | 기존 흐름에서 해당하는 단계 |
|---|---|---|
| 1️⃣ 빈 정의 등록 (Definition 등록) | 어떤 빈이 존재할 것인지 스프링이 "정의"만 먼저 등록하는 단계 | ① ~ ⑦ 전체 - @ComponentScan- @EnableAutoConfiguration- AutoProxyRegistrar, TransactionInterceptor 등도 이 시점 등록 |
| 2️⃣ 빈 인스턴스 생성 | 정의된 빈을 바탕으로 객체를 new해서 인스턴스를 만드는 단계 | ⑧ 사용자 정의 빈 생성 시점 예: @Service, @Component 클래스 |
| 3️⃣ 의존성 주입 | 생성된 빈에 필요한 의존 객체를 @Autowired, 생성자 등으로 주입 | ⑧과 함께 진행 |
| 4️⃣ 초기화 (PostProcessor 포함) | InitializingBean.afterPropertiesSet(), @PostConstruct+ BeanPostProcessor 적용→ 이 시점에 프록시 감싸짐 | ⑨ - InfrastructureAdvisorAutoProxyCreator 작동- @Transactional 조건 만족 시 프록시로 감쌈 |
| 5️⃣ 사용 | 사용자가 메서드 호출 → 프록시가 가로채서 트랜잭션 처리 시작 | 🔟 이후 - TransactionInterceptor.invoke()- invokeWithinTransaction() 내부에서 트랜잭션 시작, 커밋, 롤백 |
| 6️⃣ 소멸 | 빈이 컨테이너에서 제거될 때 실행@PreDestroy, DisposableBean.destroy() 등 | 트랜잭션과는 직접적인 관련 없음 |
@Transactional의 실제 적용 타이밍은 빈 초기화 직전인 BeanPostProcessor 단계에서 발생.