乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@EnableTransactionManagement

定义与作用

@EnableTransactionManagement 是 Spring 框架中开启注解驱动声明式事务管理的开关注解。在纯 Spring Framework 项目中,必须在某个 @Configuration 配置类上添加该注解,Spring 容器才会识别并处理 @Transactional 注解,为其创建事务代理。在 Spring Boot 2.x 中,该注解通常由 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa 的自动配置隐式提供,开发者无需手动添加,但理解其底层机制对于排查事务失效问题至关重要。

该注解的核心作用是向 Spring 容器注册两类基础设施:

  1. TransactionInterceptor:AOP 方法拦截器,负责在代理对象的方法调用前后开启、提交或回滚事务;
  2. BeanPostProcessor(具体为 InfrastructureAdvisorAutoProxyCreator):识别带有 @Transactional 注解的 Bean,为其生成 JDK 动态代理或 CGLIB 代理。

适用位置与常用属性

适用位置

位置效果
@Configuration 类该类所在配置类及其导入的配置中,所有 @Transactional 生效
@Component 类不推荐,虽可生效但语义混乱

常用属性

属性类型默认值说明
proxyTargetClassbooleanfalse是否强制使用 CGLIB 代理(true 时即使实现接口也用 CGLIB)
modeAdviceModePROXY代理模式:PROXY(运行时代理)或 ASPECTJ(编译期/加载期织入)
orderintInteger.MAX_VALUE事务 Advisor 的优先级顺序,数值越小优先级越高

配置示例:

@Configuration
@EnableTransactionManagement(proxyTargetClass = true, order = 1)
public class TransactionConfig {
    // ...
}

Spring Boot 2.x 默认行为:spring.aop.proxy-target-class=true,即优先使用 CGLIB 代理。这与 Spring Framework 的默认行为(优先 JDK 代理)不同。

核心原理:事务基础设施的注册与代理创建

整体架构

关键组件详解

1. AutoProxyRegistrar

向容器注册 InfrastructureAdvisorAutoProxyCreator,这是一个 BeanPostProcessor。它在每个 Bean 初始化后检查该 Bean 是否需要创建代理(即是否存在 @Transactional 注解的方法)。

2. ProxyTransactionManagementConfiguration

向容器注册三个核心 Bean:

  • AnnotationTransactionAttributeSource:解析 @Transactional 注解,提取传播行为、隔离级别、超时、回滚规则等属性;
  • TransactionInterceptor:AOP 的 MethodInterceptor 实现,是实际执行事务开启、提交、回滚的代码;
  • BeanFactoryTransactionAttributeSourceAdvisor:将 Pointcut(匹配 @Transactional 方法)和 Advice(TransactionInterceptor)组合为 Advisor。

3. InfrastructureAdvisorAutoProxyCreator

Spring AOP 的自动代理创建器。当检测到 Bean 存在事务 Advisor 时,调用 ProxyFactory 创建代理对象。代理方式由 proxyTargetClass 决定:

  • proxyTargetClass = false(默认):若目标类实现接口,使用 JDK 动态代理;否则使用 CGLIB;
  • proxyTargetClass = true:强制使用 CGLIB 代理,生成目标类的子类。

代理方式的选择与影响

关键结论:无论 JDK 代理还是 CGLIB 代理,同类内部 this.method() 调用都不会触发事务拦截。这是 Spring AOP 的固有约束,与代理方式无关。

完整示例:飞翔科技手动配置事务管理

场景简述

飞翔科技公司的 白歌(架构师)正在搭建一个不使用 Spring Boot 的纯 Spring Framework 5.x 项目,用于内部工具平台。小崔(后端开发)需要在配置中手动开启事务管理,并注册 DataSourceTransactionManager。

操作前:项目依赖(纯 Spring,非 Spring Boot)

<dependencies>
    <!-- Spring Core -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.30</version>
    </dependency>
    <!-- Spring JDBC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.30</version>
    </dependency>
    <!-- Spring TX -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.30</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>4.0.3</version>
    </dependency>
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>

配置类:手动开启事务管理

package com.feixiang.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan("com.feixiang")
@EnableTransactionManagement(proxyTargetClass = true)  // 开启注解事务管理,强制 CGLIB 代理
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/feixiang_tools?useSSL=false&serverTimezone=Asia/Shanghai");
        config.setUsername("root");
        config.setPassword("secret");
        config.setMaximumPoolSize(5);
        return new HikariDataSource(config);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        // 注册基于 JDBC 的事务管理器
        return new DataSourceTransactionManager(dataSource);
    }
}

关键理解:@EnableTransactionManagement 本身不注册 PlatformTransactionManager,它只是开启对 @Transactional 注解的解析和代理创建。PlatformTransactionManager(如 DataSourceTransactionManager、JpaTransactionManager)必须由开发者显式注册,否则 Spring 会在运行时抛出 NoSuchBeanDefinitionException。

Service 层:验证事务生效

package com.feixiang.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ToolRentalService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void rentTool(int employeeId, int toolId) {
        // 1. 检查工具是否可用
        Integer available = jdbcTemplate.queryForObject(
            "SELECT available FROM tool_inventory WHERE tool_id = ?",
            Integer.class, toolId
        );
        if (available == null || available <= 0) {
            throw new IllegalStateException("工具已被借完,ID:" + toolId);
        }

        // 2. 扣减库存
        jdbcTemplate.update(
            "UPDATE tool_inventory SET available = available - 1 WHERE tool_id = ?",
            toolId
        );
        System.out.println("[工具借用] 库存已扣减,工具:" + toolId);

        // 3. 插入借用记录
        jdbcTemplate.update(
            "INSERT INTO tool_rental (employee_id, tool_id, status) VALUES (?, ?, 'RENTED')",
            employeeId, toolId
        );
        System.out.println("[工具借用] 记录已创建,员工:" + employeeId);

        // 4. 模拟异常,验证回滚
        if (toolId == 9999) {
            throw new RuntimeException("模拟系统异常,验证事务回滚");
        }
    }
}

测试执行与结果

package com.feixiang;

import com.feixiang.config.AppConfig;
import com.feixiang.service.ToolRentalService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class EnableTxTest {
    public static void main(String[] args) {
        // 使用纯 Java Config 启动 Spring 容器
        AnnotationConfigApplicationContext ctx =
            new AnnotationConfigApplicationContext(AppConfig.class);
        ToolRentalService service = ctx.getBean(ToolRentalService.class);

        // 验证代理类型
        System.out.println("Service 实际类型:" + service.getClass().getName());

        // 场景一:正常借用
        try {
            service.rentTool(1001, 1001);
            System.out.println("[场景一] 借用成功");
        } catch (Exception e) {
            System.err.println("[场景一] 借用失败:" + e.getMessage());
        }

        // 场景二:触发异常,验证回滚
        try {
            service.rentTool(1002, 9999);
            System.out.println("[场景二] 借用成功");
        } catch (Exception e) {
            System.err.println("[场景二] 借用失败:" + e.getMessage());
        }

        ctx.close();
    }
}

操作后:控制台输出与数据变化

控制台输出:

Service 实际类型:com.feixiang.service.ToolRentalService$$EnhancerBySpringCGLIB$$3a2f4b1c
[工具借用] 库存已扣减,工具:1001
[工具借用] 记录已创建,员工:1001
[场景一] 借用成功
[工具借用] 库存已扣减,工具:9999
[工具借用] 记录已创建,员工:1002
[场景二] 借用失败:模拟系统异常,验证事务回滚

关键观察:Service 的实际类型为 ...$$EnhancerBySpringCGLIB$$...,证明 CGLIB 代理已生效。这是 @EnableTransactionManagement(proxyTargetClass = true) 的直接结果。

场景一数据库状态:

tool_inventory 表:

tool_idtool_nameavailable
1001高性能笔记本4(从 5 扣减)

tool_rental 表:

rental_idemployee_idtool_idstatus
110011001RENTED

场景二数据库状态:

tool_inventory 表:

tool_idtool_nameavailable
1001高性能笔记本4
9999测试工具10(未变化,回滚生效)

tool_rental 表:无新增记录(第二条记录被回滚)。

易错场景与面试考点

反例一:忘记添加 @EnableTransactionManagement

@Configuration
@ComponentScan("com.feixiang")
// 错误:缺少 @EnableTransactionManagement
public class AppConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

@Service
public class OrderService {

    @Transactional  // 该注解被完全忽略!
    public void createOrder() {
        // ...
    }
}

问题分析:即使注册了 PlatformTransactionManager,如果没有 @EnableTransactionManagement,Spring 不会注册 TransactionInterceptor 和自动代理创建器。@Transactional 注解会被静默忽略,方法以非事务方式执行。这是事务失效的头号原因,且没有任何运行时警告,极难排查。

排查方法:检查 Bean 的实际类型。如果 orderService.getClass().getName() 输出的是原始类名(如 com.feixiang.service.OrderService)而非代理类名(含 Proxy 或 Enhancer),说明代理未创建,事务未生效。

反例二:未注册 PlatformTransactionManager

@Configuration
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        // ...
    }

    // 错误:忘记注册 PlatformTransactionManager
    // Spring 启动时会抛出 NoSuchBeanDefinitionException
}

问题分析:@EnableTransactionManagement 只负责解析 @Transactional 和创建代理,实际的事务开启、提交、回滚需要 PlatformTransactionManager 完成。如果容器中不存在该类型的 Bean,Spring 在创建 TransactionInterceptor 时会尝试按类型注入,最终抛出 NoSuchBeanDefinitionException 导致容器启动失败。

正确写法:必须显式注册与持久层技术匹配的事务管理器:

持久层技术事务管理器
JDBC / MyBatisDataSourceTransactionManager
JPA / HibernateJpaTransactionManager
JTA 分布式事务JtaTransactionManager

反例三:Spring Boot 中重复添加 @EnableTransactionManagement

@SpringBootApplication
@EnableTransactionManagement  // 冗余,Spring Boot 已自动配置
public class FeixiangApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeixiangApplication.class, args);
    }
}

问题分析:Spring Boot 的 TransactionAutoConfiguration 已经根据类路径条件自动注册了 @EnableTransactionManagement 和 DataSourceTransactionManager(或 JpaTransactionManager)。手动添加该注解不会导致错误,但属于冗余配置。如果手动添加时指定了不同的属性(如 proxyTargetClass = false),可能与 Spring Boot 的自动配置冲突,导致代理行为不一致。

正确做法:Spring Boot 项目无需手动添加 @EnableTransactionManagement,除非需要覆盖默认的 proxyTargetClass 或 mode 配置。

面试高频考点

Q1:@EnableTransactionManagement 的作用是什么?不添加会怎样?

它是开启 Spring 注解驱动声明式事务的开关。不添加时,Spring 容器不会注册 TransactionInterceptor 和自动代理创建器,@Transactional 注解被完全忽略,方法以非事务方式执行。即使注册了 PlatformTransactionManager,没有该注解事务也不会生效。

Q2:Spring Boot 为什么不需要手动添加 @EnableTransactionManagement?

Spring Boot 的 TransactionAutoConfiguration 通过条件注解 @ConditionalOnClass(PlatformTransactionManager.class) 和 @ConditionalOnBean(PlatformTransactionManager.class),在检测到 spring-tx 和事务管理器 Bean 时,自动导入 EnableTransactionManagementConfiguration,等效于添加了 @EnableTransactionManagement。

Q3:proxyTargetClass = true 和 false 有什么区别?

false(Spring Framework 默认)时,若目标类实现接口,使用 JDK 动态代理(基于接口);否则使用 CGLIB(基于子类继承)。true(Spring Boot 2.x 默认)时,强制使用 CGLIB 代理,无论是否实现接口。CGLIB 代理可以代理类级别的方法(非接口方法),但无法代理 final 类和方法。

Q4:如何排查 @Transactional 没有生效?

三步排查法:① 检查配置类是否有 @EnableTransactionManagement(非 Spring Boot 项目);② 检查是否注册了 PlatformTransactionManager;③ 打印 Bean 的实际类型(bean.getClass().getName()),确认是否为代理类(含 Proxy 或 Enhancer)。如果实际类型是原始类,说明代理未创建,需检查方法是否为 public、是否存在同类内部调用等问题。

上一页
数据访问概述
下一页
@Transactional