在企业级项目中,「数据一致性」是底线——比如用户下单时,既要扣减库存,也要生成订单,还要扣减余额,这三个操作必须同时成功、同时失败,只要有一个环节出错,所有操作都要回滚,否则就会出现「库存扣了但订单没生成」「余额扣了但库存没减少」的严重bug。
而实现这种「要么全成、要么全败」的机制,就是事务管理。SpringBoot 简化了事务配置,一个@Transactional注解就能实现事务控制,但很多开发者只知道「加个注解就有事务」,却不懂其底层原理、核心属性,导致项目中频繁出现「事务失效」的问题,排查起来费时费力。
一、什么是事务?核心特性是什么?
在讲 @Transactional 之前,我们先明确事务的核心概念——事务(Transaction)是数据库操作的最小单元,是一组不可分割的操作集合,这组操作要么全部执行成功,要么全部执行失败,不会出现「部分成功、部分失败」的情况。
比如前面提到的下单场景,「扣库存、生成订单、扣余额」这三个操作,就构成一个事务:
• 所有操作都成功:库存减少、订单生成、余额减少,事务正常提交;
• 任意一个操作失败(比如扣余额时用户余额不足):所有操作全部回滚,库存恢复、订单不生成、余额不变,保证数据一致性。
1. 事务的四大核心特性(ACID)
事务的四大特性是事务管理的基础,也是面试高频考点,必须牢记,用通俗的语言拆解,不用死记硬背:
•原子性(Atomicity):事务是不可分割的最小单元,要么全部执行,要么全部回滚,不存在中间状态(比如不能出现「扣了库存但没生成订单」);
•一致性(Consistency):事务执行前后,数据库的完整性约束不被破坏(比如下单前库存100、余额500,下单后库存99、余额400,数据始终合法);
•隔离性(Isolation):多个事务同时执行时,彼此互不干扰,一个事务的执行结果不会被另一个事务干扰(后面详细讲隔离级别);
•持久性(Durability):事务执行成功后,对数据库的修改会永久保存,即使数据库崩溃,重启后数据也不会丢失。
补充说明:SpringBoot 事务管理的底层,是基于 Spring AOP 实现的——通过动态代理(JDK/CGLIB),在目标方法执行前后,自动添加事务的开启、提交、回滚逻辑,这也是 @Transactional 注解能「无侵入式」实现事务的核心原因。
2. SpringBoot 事务的分类
SpringBoot 支持两种事务管理方式,实际开发中以注解式为主,XML式基本淘汰:
•编程式事务:通过手动编写代码控制事务(比如使用
TransactionTemplate),灵活性高,但侵入业务代码,适合复杂的事务场景(比如多事务嵌套、动态控制事务开关);•注解式事务:通过
@Transactional注解实现事务控制,无侵入式,配置简单,适合90%以上的企业级开发场景,也是我们本篇重点讲解的内容。
二、@Transactional 底层原理
很多人用 @Transactional 只知道「加注解就有事务」,但遇到事务失效时一脸懵,核心原因就是不懂其底层原理。其实 @Transactional 的底层的是「Spring AOP 动态代理 + 事务管理器」,我们拆解核心执行流程,一看就懂:
1. 核心执行流程
1. Spring 容器启动时,会扫描带有
@Transactional注解的类或方法,为其生成动态代理对象(JDK 动态代理或 CGLIB 动态代理,和 Spring AOP 代理逻辑一致);2. 当调用被 @Transactional 注解标记的方法时,实际上是调用代理对象的方法;
3. 代理对象在方法执行前,会通过事务管理器(TransactionManager)开启事务,获取数据库连接,设置事务隔离级别、传播行为等;
4. 执行目标方法的核心业务逻辑(比如扣库存、生成订单);
5. 如果方法正常执行结束,没有抛出异常,代理对象会通过事务管理器提交事务,释放数据库连接;
6. 如果方法执行过程中抛出异常,且异常类型符合回滚条件,代理对象会通过事务管理器回滚事务,释放数据库连接;
7. 如果异常类型不符合回滚条件(比如自定义异常未配置回滚),则事务不会回滚,会正常提交。
2. 事务管理器(TransactionManager)
事务管理器是 SpringBoot 事务管理的核心组件,负责事务的开启、提交、回滚,SpringBoot 会根据项目中的数据源自动配置事务管理器,无需手动配置:
• 如果使用 SpringBoot 内置的 JDBC、MyBatis,会自动配置
DataSourceTransactionManager;• 如果使用 JPA,会自动配置
JpaTransactionManager;• 如果是多数据源场景,需要手动配置多个事务管理器,并指定对应的事务管理器(后面实战演示)。
核心结论:@Transactional 注解本身不实现事务,只是一个「标记」,告诉 Spring 这个方法需要被事务增强,真正实现事务的是「事务管理器 + 动态代理」。
三、@Transactional 核心属性详解
@Transactional 注解有多个核心属性,合理配置这些属性,能满足不同场景的事务需求;配置不当,就会导致事务失效或不符合预期。我们结合企业实战场景,逐一讲解每个属性的用法和注意事项。
1. value / transactionManager(指定事务管理器)
「value」和「transactionManager」作用一致,都是指定事务管理器的Bean名称,适用于多数据源场景。
✅ 用法示例:
// 多数据源场景,指定名为 "db1TransactionManager" 的事务管理器 @Transactional(transactionManager = "db1TransactionManager") public void addOrder(Order order) { // 业务逻辑:扣库存、生成订单 }❌ 注意事项:
单数据源场景下,无需指定,SpringBoot 会自动使用默认的事务管理器;多数据源场景下,必须指定,否则会报错或事务失效。
2. propagation(事务传播行为)
事务传播行为定义了「当一个带有事务的方法,调用另一个带有事务的方法时,事务如何传递」,这是企业级开发中最常用、也最容易出错的属性之一。
Spring 提供了7种事务传播行为,我们重点讲解5种常用的,其余两种(SUPPORTS、NOT_SUPPORTED)几乎不用,可忽略:
传播行为 | 说明(通俗版) | 企业实战场景 |
REQUIRED(默认) | 如果当前有事务,就加入当前事务;如果当前没有事务,就新建一个事务。 | 最常用场景:下单、支付、新增数据等,确保方法在一个事务中执行。 |
REQUIRES_NEW | 无论当前是否有事务,都新建一个独立的事务;原有事务会被挂起,新事务执行完成后,再恢复原有事务。 | 日志记录、消息发送等:即使主事务回滚,日志也必须保存(比如下单失败,也要记录失败日志)。 |
NESTED | 如果当前有事务,就在当前事务中嵌套一个子事务;子事务回滚不会影响主事务,但主事务回滚会带动子事务回滚。 | 复杂业务拆分:比如下单时,先扣库存(子事务),再生成订单(主事务),扣库存失败回滚,不影响主事务后续操作。 |
MANDATORY | 必须在一个已存在的事务中执行,否则抛出异常(NoTransactionException)。 | 依赖主事务的子方法:比如订单支付后,修改订单状态,必须在支付事务中执行,不能单独执行。 |
NEVER | 必须在没有事务的环境中执行,否则抛出异常。 | 无需事务的方法:比如查询数据、获取字典信息,不允许有事务(避免占用数据库连接)。 |
重点提醒:默认传播行为是 REQUIRED,很多事务失效问题,都是因为传播行为配置不当(比如子方法用了 REQUIRES_NEW,主事务回滚后,子事务没有回滚)。
3. isolation(事务隔离级别)
事务隔离级别解决的是「多个事务同时执行时,彼此之间的干扰问题」,数据库默认的隔离级别是「READ COMMITTED」(读已提交),SpringBoot 也默认使用该级别。
先明确:多个事务并发执行时,可能出现的3个问题(脏读、不可重复读、幻读):
•脏读:一个事务读取到另一个事务未提交的数据(比如事务A扣减余额但未提交,事务B读取到扣减后的余额,之后事务A回滚,事务B读取到的就是「脏数据」);
•不可重复读:同一个事务中,多次读取同一数据,结果不一致(比如事务A读取余额为500,事务B修改余额为400并提交,事务A再次读取余额为400);
•幻读:同一个事务中,多次查询同一条件的数据,结果的条数不一致(比如事务A查询库存大于10的商品有5个,事务B新增1个库存大于10的商品并提交,事务A再次查询变成6个)。
Spring 支持5种事务隔离级别,对应解决上述问题,结合数据库支持情况(MySQL 支持4种,Oracle 支持2种),详解如下:
隔离级别 | 说明 | 解决的问题 | 数据库支持情况 |
DEFAULT(默认) | 使用数据库默认的隔离级别(MySQL 默认 READ COMMITTED,Oracle 默认 READ COMMITTED) | 解决脏读,无法解决不可重复读、幻读 | 所有数据库支持 |
READ_UNCOMMITTED(读未提交) | 允许读取未提交的事务数据 | 无,会出现脏读、不可重复读、幻读 | MySQL、PostgreSQL 支持 |
READ_COMMITTED(读已提交) | 只允许读取已提交的事务数据 | 解决脏读,无法解决不可重复读、幻读 | 所有主流数据库支持(MySQL、Oracle、PostgreSQL) |
REPEATABLE_READ(可重复读) | 同一个事务中,多次读取同一数据,结果一致 | 解决脏读、不可重复读,MySQL 中可解决幻读(通过MVCC机制) | MySQL 支持,Oracle 不支持 |
SERIALIZABLE(串行化) | 所有事务串行执行,不允许并发 | 解决所有并发问题(脏读、不可重复读、幻读) | 所有数据库支持,但性能极差,几乎不用 |
💡 企业实战建议:默认使用 READ COMMITTED 即可,满足大部分场景;如果是金融、电商等对数据一致性要求极高的场景,可使用 REPEATABLE_READ(MySQL);严禁使用 SERIALIZABLE(性能太差,会导致并发瓶颈)。
4. rollbackFor / noRollbackFor(事务回滚控制)
这两个属性用于控制「哪些异常会触发事务回滚,哪些异常不会触发回滚」,是解决「事务不回滚」问题的关键。
✅ 核心规则:
• Spring 事务默认只对「 unchecked 异常」回滚(即 RuntimeException 及其子类);
• 对「 checked 异常」(即 Exception 及其子类,除了 RuntimeException)不回滚(比如 IOException、SQLException);
• rollbackFor:指定需要回滚的异常类型(可指定多个),即使是 checked 异常,也会回滚;
• noRollbackFor:指定不需要回滚的异常类型(可指定多个),即使是 unchecked 异常,也不会回滚。
✅ 代码示例:
// 指定所有 Exception 及其子类都回滚(避免漏回滚) @Transactional(rollbackFor = Exception.class) public void addOrder(Order order) throws IOException { // 业务逻辑:扣库存、生成订单 // 如果抛出 IOException(checked异常),事务也会回滚 } // 排除特定异常,不回滚 @Transactional(rollbackFor = Exception.class, noRollbackFor = BusinessException.class) public void updateOrder(Order order) { // 业务逻辑 // 如果抛出 BusinessException(自定义异常),事务不回滚;其他异常回滚 }避坑提醒:实际开发中,建议统一配置rollbackFor = Exception.class,避免因忘记处理 checked 异常导致事务不回滚(比如查询数据库时抛出 SQLException,默认不回滚,会导致数据不一致)。
5. timeout(事务超时时间)
指定事务的超时时间(单位:秒),如果事务执行时间超过该时间,就会自动回滚,并抛出 TransactionTimedOutException 异常。
✅ 用法示例:
// 事务超时时间为3秒,超过3秒自动回滚 @Transactional(rollbackFor = Exception.class, timeout = 3) public void batchImportData(List<Data> dataList) { // 批量导入数据(耗时操作,防止长时间占用数据库连接) }❌ 注意事项:
timeout 的计时起点是「事务开启」,终点是「事务提交/回滚」;如果方法中存在非事务操作(比如调用第三方接口),也会计入超时时间,需合理设置。
6. readOnly(只读事务)
指定事务为「只读事务」,适用于只查询数据、不修改数据的场景(比如列表查询、详情查询)。
✅ 用法示例:
// 只读事务,只能查询,不能执行 insert/update/delete @Transactional(readOnly = true) public List<Order> getOrderList(Long userId) { return orderMapper.selectByUserId(userId); }✅ 核心作用:
• 优化性能:数据库对只读事务会做优化(比如不开启事务日志、不锁定数据);
• 防止误操作:如果在只读事务中执行 insert/update/delete,会抛出异常(不同数据库表现不同,MySQL 会允许执行,但不推荐)。
❌ 注意事项:readOnly = true 只能用于查询方法,不能用于修改方法(insert、update、delete),否则会导致事务异常或数据不一致。
四、SpringBoot 事务实战
结合前面的属性讲解,我们用一个「下单场景」实战,完整实现事务控制,包含单数据源、多数据源、事务嵌套三种场景,直接复用即可。
1. 环境准备(单数据源)
首先导入依赖(SpringBoot 2.x+),无需额外配置事务管理器,SpringBoot 会自动配置:
<!-- SpringBoot -web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- lombok(简化代码) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>配置 application.yml(数据源配置):
spring: datasource: url: jdbc:mysql://localhost:3306/springboot_transaction?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.transaction.entity configuration: map-underscore-to-camel-case: true # 下划线转驼峰2. 单数据源实战(下单场景)
需求:用户下单,需要执行3个操作:扣减库存、生成订单、扣减余额,三个操作必须在同一个事务中,任意一个失败,全部回滚;同时记录下单日志(日志必须保存,即使主事务回滚)。
步骤1:定义实体类(Order、Stock、User、OrderLog),此处省略,可根据实际需求定义。
步骤2:定义 Mapper 接口(OrderMapper、StockMapper、UserMapper、OrderLogMapper),此处省略,核心是实现 insert、update 操作。
步骤3:编写 Service 层(事务配置):
@Service @Slf4j public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private StockMapper stockMapper; @Autowired private UserMapper userMapper; @Autowired private OrderLogService orderLogService; // 主事务:下单操作,默认传播行为 REQUIRED @Transactional(rollbackFor = Exception.class, timeout = 5) public void createOrder(OrderDTO orderDTO) { try { // 1. 扣减库存 int stockRows = stockMapper.decreaseStock(orderDTO.getProductId(), orderDTO.getQuantity()); if (stockRows == 0) { throw new BusinessException("库存不足"); } // 2. 生成订单 Order order = new Order(); order.setUserId(orderDTO.getUserId()); order.setProductId(orderDTO.getProductId()); order.setQuantity(orderDTO.getQuantity()); order.setOrderNo(UUID.randomUUID().toString().replace("-", "")); order.setCreateTime(new Date()); orderMapper.insert(order); // 3. 扣减余额 int userRows = userMapper.decreaseBalance(orderDTO.getUserId(), orderDTO.getTotalAmount()); if (userRows == 0) { throw new BusinessException("用户余额不足"); } // 4. 记录下单日志(独立事务,即使主事务回滚,日志也保存) orderLogService.recordOrderLog(order.getOrderNo(), "下单成功"); } catch (Exception e) { log.error("下单失败:{}", e.getMessage(), e); // 手动抛出异常,触发事务回滚(如果是 unchecked 异常,可省略) throw new RuntimeException(e.getMessage()); } } } // 日志 Service:独立事务,传播行为 REQUIRES_NEW @Service @Slf4j public class OrderLogService { @Autowired private OrderLogMapper orderLogMapper; // 独立事务,无论主事务是否回滚,日志都提交 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void recordOrderLog(String orderNo, String content) { OrderLog orderLog = new OrderLog(); orderLog.setOrderNo(orderNo); orderLog.setContent(content); orderLog.setCreateTime(new Date()); orderLogMapper.insert(orderLog); } }步骤4:编写 Controller 层,调用 Service 方法:
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/create") public ResultVO createOrder(@RequestBody OrderDTO orderDTO) { try { orderService.createOrder(orderDTO); return ResultVO.success("下单成功"); } catch (BusinessException e) { return ResultVO.fail(e.getMessage()); } catch (Exception e) { return ResultVO.fail("系统异常,请稍后重试"); } } }✅ 测试验证:
• 正常情况:库存充足、余额充足,三个操作都成功,日志也成功记录;
• 异常情况1:库存不足,抛出 BusinessException,主事务回滚(库存、订单、余额都不变),但日志记录成功(因为日志是独立事务);
• 异常情况2:余额不足,抛出 BusinessException,主事务回滚,日志记录成功。
3. 多数据源事务
如果项目中存在多数据源(比如订单库、用户库),需要手动配置多个事务管理器,并指定对应的事务管理器:
// 多数据源配置(省略数据源配置,仅展示事务管理器) @Configuration public class TransactionConfig { // 订单库事务管理器 @Bean(name = "orderTransactionManager") public PlatformTransactionManager orderTransactionManager(@Qualifier("orderDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } // 用户库事务管理器 @Bean(name = "userTransactionManager") public PlatformTransactionManager userTransactionManager(@Qualifier("userDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } // Service 中指定事务管理器 @Service public class OrderService { // 操作订单库,指定订单库事务管理器 @Transactional(rollbackFor = Exception.class, transactionManager = "orderTransactionManager") public void createOrder(Order order) { orderMapper.insert(order); } } @Service public class UserService { // 操作用户库,指定用户库事务管理器 @Transactional(rollbackFor = Exception.class, transactionManager = "userTransactionManager") public void decreaseBalance(Long userId, BigDecimal amount) { userMapper.decreaseBalance(userId, amount); } }五、文末小结
@Transactional 注解看似简单,实则包含很多细节,想要用好它,核心要抓住3点:
1. 懂原理:知道其底层是 Spring AOP 动态代理 + 事务管理器,明白代理对象是事务生效的关键;
2. 会配置:掌握核心属性(传播行为、隔离级别、rollbackFor),根据业务场景合理配置;
3. 能排错:牢记8种常见的事务失效场景,遇到问题能快速定位、解决。
实际开发中,最常用的配置是@Transactional(rollbackFor = Exception.class),再根据场景补充传播行为、超时时间、只读属性等,就能满足大部分企业级需求。
另外,事务管理的核心是「保证数据一致性」,不要过度使用事务(比如查询方法不要加事务),也不要滥用传播行为(比如随意用 REQUIRES_NEW),否则会导致性能瓶颈或数据不一致。
如果你在项目中遇到事务相关的问题,或者有其他疑问,欢迎在评论区留言交流,一起避坑、一起进步!
别忘了点赞+在看+收藏三连,关注我,解锁更多 SpringBoot 实战干货,下期再见❤️