事务是保证数据一致性的核心机制,尤其在多步操作的业务场景(如订单创建同时扣减库存)中不可或缺,Spring通过AOP实现了声明式事务管理,简化了传统JDBC手动控制事务的繁琐流程。
事务(Transaction)是由一系列数据库操作组成的逻辑单元,这些操作要么全部成功,要么全部失败(如“创建订单”需同时执行“插入订单记录”和“扣减库存”,两者必须同时成功或同时失败)。
事务必须满足ACID特性,这是保证数据一致性的基础:
传统JDBC事务需要手动控制(conn.setAutoCommit(false)
→commit()
→rollback()
),而Spring事务的优势在于:
@Transactional
)或XML配置事务,无需编写事务控制代码;Spring事务的核心是@Transactional
注解,通过属性配置事务行为,常用属性如下:
属性名 | 作用 | 默认值 |
---|---|---|
propagation |
事务传播行为(如何处理嵌套事务) | Propagation.REQUIRED |
isolation |
事务隔离级别(并发控制) | Isolation.DEFAULT (数据库默认) |
readOnly |
是否为只读事务(优化性能) | false |
timeout |
事务超时时间(秒,超时自动回滚) | -1 (无超时) |
rollbackFor |
需要回滚的异常类型(如Exception.class ) |
仅RuntimeException 及其子类 |
noRollbackFor |
不需要回滚的异常类型 | 无 |
事务传播行为定义了“当一个事务方法调用另一个事务方法时,事务如何传播”,是Spring事务最核心的特性之一。
REQUIRED
(默认值)@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockService stockService;
// 传播行为:REQUIRED(默认)
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
orderMapper.insert(order); // 操作1
stockService.reduceStock(order.getProductId()); // 调用另一个事务方法
}
}
@Service
public class StockService {
// 传播行为:REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void reduceStock(Long productId) {
// 操作2:扣减库存
}
}
执行逻辑:
createOrder
创建事务→reduceStock
加入该事务→若操作1或2失败,整个事务回滚(符合原子性)。
SUPPORTS
@Service
public class UserService {
// 传播行为:SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public User getUserById(Long id) {
// 查询用户(非核心操作,有无事务均可)
}
}
REQUIRES_NEW
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional
public 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
创建新事务→日志记录成功提交→主事务若失败,仅主事务回滚,日志不会回滚。
NEVER
MANDATORY
REQUIRED
;SUPPORTS
;REQUIRES_NEW
;MANDATORY
。事务隔离级别定义了“多个事务并发执行时的隔离程度”,用于解决并发问题(脏读、不可重复读、幻读)。
问题 | 说明 | 示例 |
---|---|---|
脏读 | 读取到另一个未提交事务的修改 | 事务A修改数据→事务B读取→事务A回滚→事务B读取到无效数据 |
不可重复读 | 同一事务中多次读取数据不一致 | 事务A读取数据→事务B修改并提交→事务A再次读取,数据不同 |
幻读 | 同一事务中多次查询,结果集数量不一致 | 事务A查询所有订单→事务B新增订单并提交→事务A再次查询,多了一条记录 |
Spring支持5种隔离级别,对应数据库的隔离级别:
隔离级别 | 解决问题 | 并发性能 | 适用场景 |
---|---|---|---|
DEFAULT (默认) |
数据库默认隔离级别(如MySQL默认REPEATABLE_READ ) |
中等 | 大多数场景(推荐) |
READ_UNCOMMITTED |
无(允许脏读、不可重复读、幻读) | 最高 | 极少使用(对一致性要求极低) |
READ_COMMITTED |
解决脏读 | 较高 | 对一致性有基本要求(如Oracle默认) |
REPEATABLE_READ |
解决脏读、不可重复读 | 中等 | MySQL默认,大多数业务场景 |
SERIALIZABLE |
解决所有问题(串行执行) | 最低 | 对一致性要求极高(如金融交易) |
@Service
public class OrderService {
// 设置隔离级别为REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void createOrder(Order order) {
// 业务逻辑
}
}
注意:隔离级别受数据库支持限制(如MySQL支持所有级别,SQL Server不支持READ_UNCOMMITTED
),实际以数据库为准。
以“订单创建”为例,演示事务的完整使用(包含传播行为、异常回滚配置)。
添加Spring事务依赖(已包含在spring-context
中),并配置数据源和事务管理器:
@Configuration
@MapperScan("com.example.mapper")
public class SpringConfig {
// 数据源配置(省略,需配置正确的JDBC连接)
@Bean
public DataSource dataSource() { ... }
// 事务管理器(核心)
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
public interface OrderMapper {
void insert(Order order);
}
public interface StockMapper {
void reduceStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private 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 {
@Autowired
private 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("库存不足");
}
}
}
@Test
public void testCreateOrderSuccess() {
Order order = new Order();
order.setProductId(1L);
order.setQuantity(2);
order.setTotalAmount(100.0);
orderService.createOrder(order);
// 验证:订单表新增记录,库存表数量减少(事务提交成功)
}
@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) {
// 验证:订单表无新增记录,库存表数量未变(事务回滚成功)
}
}
问题:方法抛出CheckedException
(如Exception
),但rollbackFor
未配置,导致事务不回滚。
原因:@Transactional
默认只对RuntimeException
及其子类回滚,对CheckedException
不回滚。
解决方案:
通过rollbackFor
指定需要回滚的异常:
// 对所有Exception回滚
@Transactional(rollbackFor = Exception.class)
问题:非public方法(如private
、protected
)的@Transactional
无效。
原因:Spring AOP默认只对public方法增强(事务基于AOP实现)。
解决方案:
确保事务方法为public
。
问题:同一类中方法调用(自调用)时,事务不生效。
@Service
public class OrderService {
public void methodA() {
methodB(); // 自调用,事务不生效
}
@Transactional
public void methodB() { ... }
}
原因:Spring事务基于代理对象,自调用是目标对象内部调用,未经过代理。
解决方案:
@Service
public class OrderService {
@Autowired
private OrderService orderService; // 注入自身代理
public void methodA() {
orderService.methodB(); // 通过代理调用,事务生效
}
@Transactional
public void methodB() { ... }
}
@EnableAspectJAutoProxy(exposeProxy = true)
),通过AopContext
获取代理:public void methodA() {
((OrderService) AopContext.currentProxy()).methodB();
}
问题:事务执行时间过长,占用数据库连接,导致连接池耗尽。
解决方案:
设置合理的超时时间(秒),超时自动回滚并释放连接:
// 超时时间30秒
@Transactional(timeout = 30)
public void createOrder(Order order) { ... }
对查询方法设置readOnly = true
,提示数据库优化事务(如避免写操作、启用缓存):
// 只读事务(查询方法)
@Transactional(readOnly = true)
public List<Order> getOrders() { ... }
注意:只读事务中执行insert
/update
会抛出异常(取决于数据库)。
总结:事务使用的最佳实践
Spring事务简化了事务管理,但需遵循最佳实践避免常见问题:
- 核心配置:
- 对所有业务方法显式指定
rollbackFor = Exception.class
(避免异常不回滚);- 核心业务用
REQUIRED
传播行为,独立操作(如日志)用REQUIRES_NEW
;- 查询方法设置
readOnly = true
优化性能。
- 避坑要点:
- 事务方法必须为
public
;- 避免自调用(或通过代理对象调用);
- 合理设置超时时间,避免长事务。
- 性能优化:
- 事务范围尽可能小(仅包含必要操作,避免在事务中调用外部接口、等待用户输入);
- 读多写少场景用较高隔离级别(如
READ_COMMITTED
),减少锁竞争。事务是保证数据一致性的最后一道防线,正确使用能避免数据错乱(如重复下单、库存超卖)等严重问题。建议在开发中结合日志(如打印事务开始/提交/回滚日志)和测试(模拟异常验证回滚)确保事务生效。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ