Spring之事务使用指南
- 一、事务的基础概念
- 1.1 什么是事务?
- 1.2 事务的ACID特性
- 1.3 Spring事务的核心优势
- 二、Spring事务的核心配置
- 三、事务传播行为(Propagation)
- 3.1 常用传播行为详解
- 3.1.1 `REQUIRED`(默认值)
- 3.1.2 `SUPPORTS`
- 3.1.3 `REQUIRES_NEW`
- 3.1.4 `NEVER`
- 3.1.5 `MANDATORY`
- 3.2 传播行为选择原则
- 四、事务隔离级别(Isolation)
- 4.1 并发事务的三大问题
- 4.2 隔离级别详解
- 4.3 配置隔离级别
- 五、声明式事务实战
- 5.1 环境准备
- 5.2 业务实现
- 5.2.1 Mapper接口(MyBatis)
- 5.2.2 Service实现
- 5.3 测试与验证
- 5.3.1 正常流程(无异常)
- 5.3.2 异常流程(触发回滚)
- 六、常见问题与避坑指南
- 6.1 事务不回滚(@Transactional失效)
- 6.1.1 异常类型不匹配
- 6.1.2 方法非public
- 6.1.3 自调用导致事务失效
- 6.2 事务超时(Timeout)
- 6.3 只读事务(readOnly)
事务是保证数据一致性的核心机制,尤其在多步操作的业务场景(如订单创建同时扣减库存)中不可或缺,Spring通过AOP实现了声明式事务管理,简化了传统JDBC手动控制事务的繁琐流程。
一、事务的基础概念
1.1 什么是事务?
事务(Transaction)是由一系列数据库操作组成的逻辑单元,这些操作要么全部成功,要么全部失败(如“创建订单”需同时执行“插入订单记录”和“扣减库存”,两者必须同时成功或同时失败)。
1.2 事务的ACID特性
事务必须满足ACID特性,这是保证数据一致性的基础:
- 原子性(Atomicity):事务中的操作要么全执行,要么全不执行(如转账时“扣款”和“入账”必须同时成功);
- 一致性(Consistency):事务执行前后,数据从一个有效状态变为另一个有效状态(如转账前后总金额不变);
- 隔离性(Isolation):多个事务并发执行时,彼此不干扰(避免脏读、不可重复读等问题);
- 持久性(Durability):事务提交后,修改永久保存到数据库(即使断电也不丢失)。
1.3 Spring事务的核心优势
传统JDBC事务需要手动控制(conn.setAutoCommit(false)
→commit()
→rollback()
),而Spring事务的优势在于:
- 声明式事务:通过注解(
@Transactional
)或XML配置事务,无需编写事务控制代码; - AOP实现:事务逻辑与业务逻辑分离,业务代码只关注核心逻辑;
- 灵活配置:支持自定义传播行为、隔离级别、超时时间等;
- 整合方便:与Spring容器无缝集成,支持各种数据源和ORM框架(如MyBatis、Hibernate)。
二、Spring事务的核心配置
Spring事务的核心是@Transactional
注解,通过属性配置事务行为,常用属性如下:
属性名 | 作用 | 默认值 |
---|---|---|
propagation | 事务传播行为(如何处理嵌套事务) | Propagation.REQUIRED |
isolation | 事务隔离级别(并发控制) | Isolation.DEFAULT (数据库默认) |
readOnly | 是否为只读事务(优化性能) | false |
timeout | 事务超时时间(秒,超时自动回滚) | -1 (无超时) |
rollbackFor | 需要回滚的异常类型(如Exception.class ) | 仅RuntimeException 及其子类 |
noRollbackFor | 不需要回滚的异常类型 | 无 |
三、事务传播行为(Propagation)
事务传播行为定义了“当一个事务方法调用另一个事务方法时,事务如何传播”,是Spring事务最核心的特性之一。
3.1 常用传播行为详解
3.1.1 REQUIRED
(默认值)
- 规则:如果当前存在事务,则加入该事务;如果没有事务,则创建新事务。
- 适用场景:大多数业务方法(如订单创建、用户注册)。
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StockService stockService;// 传播行为:REQUIRED(默认)@Transactional(propagation = Propagation.REQUIRED)public void createOrder(Order order) {orderMapper.insert(order); // 操作1stockService.reduceStock(order.getProductId()); // 调用另一个事务方法}
}@Service
public class StockService {// 传播行为:REQUIRED@Transactional(propagation = Propagation.REQUIRED)public void reduceStock(Long productId) {// 操作2:扣减库存}
}
执行逻辑:
createOrder
创建事务→reduceStock
加入该事务→若操作1或2失败,整个事务回滚(符合原子性)。
3.1.2 SUPPORTS
- 规则:如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式执行。
- 适用场景:可选事务的方法(如查询操作,可在事务中执行,也可单独执行)。
@Service
public class UserService {// 传播行为:SUPPORTS@Transactional(propagation = Propagation.SUPPORTS)public User getUserById(Long id) {// 查询用户(非核心操作,有无事务均可)}
}
3.1.3 REQUIRES_NEW
- 规则:无论当前是否存在事务,都创建新事务(原事务暂停,新事务独立执行)。
- 适用场景:需要独立事务的操作(如日志记录,即使主事务失败也需提交)。
@Service
public class OrderService {@Autowiredprivate LogService logService;@Transactionalpublic void createOrder(Order order) {// 主事务操作:创建订单logService.recordLog("创建订单:" + order.getId()); // 调用独立事务方法}
}@Service
public class LogService {// 传播行为:REQUIRES_NEW(独立事务)@Transactional(propagation = Propagation.REQUIRES_NEW)public void recordLog(String content) {// 记录日志(即使主事务回滚,此操作仍会提交)}
}
执行逻辑:
主事务执行→recordLog
创建新事务→日志记录成功提交→主事务若失败,仅主事务回滚,日志不会回滚。
3.1.4 NEVER
- 规则:如果当前存在事务,则抛出异常;如果没有事务,则以非事务方式执行。
- 适用场景:绝对不能在事务中执行的方法(如某些特殊查询)。
3.1.5 MANDATORY
- 规则:如果当前存在事务,则加入该事务;如果没有事务,则抛出异常。
- 适用场景:必须在事务中执行的方法(如核心业务操作)。
3.2 传播行为选择原则
- 核心业务方法(如订单、支付):
REQUIRED
; - 查询方法:
SUPPORTS
; - 独立日志、审计操作:
REQUIRES_NEW
; - 必须在事务中执行的方法:
MANDATORY
。
四、事务隔离级别(Isolation)
事务隔离级别定义了“多个事务并发执行时的隔离程度”,用于解决并发问题(脏读、不可重复读、幻读)。
4.1 并发事务的三大问题
问题 | 说明 | 示例 |
---|---|---|
脏读 | 读取到另一个未提交事务的修改 | 事务A修改数据→事务B读取→事务A回滚→事务B读取到无效数据 |
不可重复读 | 同一事务中多次读取数据不一致 | 事务A读取数据→事务B修改并提交→事务A再次读取,数据不同 |
幻读 | 同一事务中多次查询,结果集数量不一致 | 事务A查询所有订单→事务B新增订单并提交→事务A再次查询,多了一条记录 |
4.2 隔离级别详解
Spring支持5种隔离级别,对应数据库的隔离级别:
隔离级别 | 解决问题 | 并发性能 | 适用场景 |
---|---|---|---|
DEFAULT (默认) | 数据库默认隔离级别(如MySQL默认REPEATABLE_READ ) | 中等 | 大多数场景(推荐) |
READ_UNCOMMITTED | 无(允许脏读、不可重复读、幻读) | 最高 | 极少使用(对一致性要求极低) |
READ_COMMITTED | 解决脏读 | 较高 | 对一致性有基本要求(如Oracle默认) |
REPEATABLE_READ | 解决脏读、不可重复读 | 中等 | MySQL默认,大多数业务场景 |
SERIALIZABLE | 解决所有问题(串行执行) | 最低 | 对一致性要求极高(如金融交易) |
4.3 配置隔离级别
@Service
public class OrderService {// 设置隔离级别为REPEATABLE_READ@Transactional(isolation = Isolation.REPEATABLE_READ)public void createOrder(Order order) {// 业务逻辑}
}
注意:隔离级别受数据库支持限制(如MySQL支持所有级别,SQL Server不支持READ_UNCOMMITTED
),实际以数据库为准。
五、声明式事务实战
以“订单创建”为例,演示事务的完整使用(包含传播行为、异常回滚配置)。
5.1 环境准备
添加Spring事务依赖(已包含在spring-context
中),并配置数据源和事务管理器:
@Configuration
@MapperScan("com.example.mapper")
public class SpringConfig {// 数据源配置(省略,需配置正确的JDBC连接)@Beanpublic DataSource dataSource() { ... }// 事务管理器(核心)@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
5.2 业务实现
5.2.1 Mapper接口(MyBatis)
public interface OrderMapper {void insert(Order order);
}public interface StockMapper {void reduceStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
}
5.2.2 Service实现
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StockService stockService;/*** 创建订单(核心业务)* 传播行为:REQUIRED(默认)* 回滚规则:所有Exception都回滚(默认仅RuntimeException回滚,此处扩展)*/@Transactional(rollbackFor = Exception.class)public void createOrder(Order order) throws Exception {// 1. 创建订单orderMapper.insert(order);// 2. 扣减库存(调用另一个事务方法)stockService.reduceStock(order.getProductId(), order.getQuantity());// 3. 模拟异常(测试回滚)if (order.getTotalAmount() < 0) {throw new Exception("订单金额不能为负"); // 触发回滚}}
}@Service
public class StockService {@Autowiredprivate StockMapper stockMapper;// 传播行为:REQUIRED(加入订单事务)@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void reduceStock(Long productId, Integer quantity) {stockMapper.reduceStock(productId, quantity);// 若库存不足,抛出异常(触发回滚)if (/* 库存不足 */) {throw new RuntimeException("库存不足");}}
}
5.3 测试与验证
5.3.1 正常流程(无异常)
@Test
public void testCreateOrderSuccess() {Order order = new Order();order.setProductId(1L);order.setQuantity(2);order.setTotalAmount(100.0);orderService.createOrder(order);// 验证:订单表新增记录,库存表数量减少(事务提交成功)
}
5.3.2 异常流程(触发回滚)
@Test
public void testCreateOrderRollback() {Order order = new Order();order.setProductId(1L);order.setQuantity(2);order.setTotalAmount(-100.0); // 触发异常try {orderService.createOrder(order);} catch (Exception e) {// 验证:订单表无新增记录,库存表数量未变(事务回滚成功)}
}
六、常见问题与避坑指南
6.1 事务不回滚(@Transactional失效)
6.1.1 异常类型不匹配
问题:方法抛出CheckedException
(如Exception
),但rollbackFor
未配置,导致事务不回滚。
原因:@Transactional
默认只对RuntimeException
及其子类回滚,对CheckedException
不回滚。
解决方案:
通过rollbackFor
指定需要回滚的异常:
// 对所有Exception回滚
@Transactional(rollbackFor = Exception.class)
6.1.2 方法非public
问题:非public方法(如private
、protected
)的@Transactional
无效。
原因:Spring AOP默认只对public方法增强(事务基于AOP实现)。
解决方案:
确保事务方法为public
。
6.1.3 自调用导致事务失效
问题:同一类中方法调用(自调用)时,事务不生效。
@Service
public class OrderService {public void methodA() {methodB(); // 自调用,事务不生效}@Transactionalpublic void methodB() { ... }
}
原因:Spring事务基于代理对象,自调用是目标对象内部调用,未经过代理。
解决方案:
- 注入自身代理对象调用:
@Service
public class OrderService {@Autowiredprivate OrderService orderService; // 注入自身代理public void methodA() {orderService.methodB(); // 通过代理调用,事务生效}@Transactionalpublic void methodB() { ... }
}
- 开启暴露代理(
@EnableAspectJAutoProxy(exposeProxy = true)
),通过AopContext
获取代理:
public void methodA() {((OrderService) AopContext.currentProxy()).methodB();
}
6.2 事务超时(Timeout)
问题:事务执行时间过长,占用数据库连接,导致连接池耗尽。
解决方案:
设置合理的超时时间(秒),超时自动回滚并释放连接:
// 超时时间30秒
@Transactional(timeout = 30)
public void createOrder(Order order) { ... }
6.3 只读事务(readOnly)
对查询方法设置readOnly = true
,提示数据库优化事务(如避免写操作、启用缓存):
// 只读事务(查询方法)
@Transactional(readOnly = true)
public List<Order> getOrders() { ... }
注意:只读事务中执行insert
/update
会抛出异常(取决于数据库)。
总结:事务使用的最佳实践
Spring事务简化了事务管理,但需遵循最佳实践避免常见问题:
- 核心配置:
- 对所有业务方法显式指定
rollbackFor = Exception.class
(避免异常不回滚);- 核心业务用
REQUIRED
传播行为,独立操作(如日志)用REQUIRES_NEW
;- 查询方法设置
readOnly = true
优化性能。
- 避坑要点:
- 事务方法必须为
public
;- 避免自调用(或通过代理对象调用);
- 合理设置超时时间,避免长事务。
- 性能优化:
- 事务范围尽可能小(仅包含必要操作,避免在事务中调用外部接口、等待用户输入);
- 读多写少场景用较高隔离级别(如
READ_COMMITTED
),减少锁竞争。事务是保证数据一致性的最后一道防线,正确使用能避免数据错乱(如重复下单、库存超卖)等严重问题。建议在开发中结合日志(如打印事务开始/提交/回滚日志)和测试(模拟异常验证回滚)确保事务生效。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ