⭐ 掌握事务原理 | 💡 精通Spring事务 | 🔥 面试必备知识
📖 前言
💭 「数据一致性是系统的生命线,事务管理是保障」
在实际开发中,数据的一致性至关重要。想象一下银行转账:从A账户扣款后,如果系统崩溃导致B账户未收到款项,这将是灾难性的!事务管理就是为了解决这类问题而存在的。
本文将深入浅出地讲解Spring事务管理的方方面面,从基础概念到实战应用,助你完全掌握这一核心技术。
🎯 学习目标:
- ✅ 深入理解事务的四大特性(ACID)
- ✅ 掌握Spring事务管理机制
- ✅ 熟练使用XML和注解配置事务
- ✅ 理解事务传播行为和隔离级别
- ✅ 解决常见事务问题
- ✅ 应对面试高频考点
一、🎯 事务基础概念
1.1 什么是事务?
💡 通俗解释:事务就像一个"全有或全无"的操作包裹,要么所有操作都成功,要么全部失败回滚。
🔍 定义:事务(Transaction)是数据库操作的最小工作单元,是一系列操作的集合,这些操作要么全部成功,要么全部失败。
🎭 生活中的例子:
场景:银行转账
操作步骤:
1. 检查A账户余额是否充足
2. 从A账户扣除1000元
3. 向B账户增加1000元
4. 记录转账日志
问题:如果第2步成功,但第3步失败(系统崩溃),怎么办?
答案:事务会回滚,让A账户的钱恢复原状!
🌟 为什么需要事务?
- 保证数据一致性:避免部分成功导致的数据混乱
- 提供隔离性:防止并发操作相互干扰
- 确保持久性:成功的操作永久保存
- 支持回滚:失败时可以恢复到之前状态
1.2 事务的使用场景
java
// 🎯 场景1:电商下单
public void createOrder(Order order) {
// 1. 扣减库存
// 2. 创建订单
// 3. 扣减用户积分
// 4. 发送消息通知
// 这些操作必须在一个事务中!
}
// 🎯 场景2:用户注册
public void registerUser(User user) {
// 1. 创建用户账号
// 2. 初始化用户钱包
// 3. 发送欢迎邮件
// 前两步需要事务保护
}
// 🎯 场景3:批量数据导入
public void batchImport(List<Data> dataList) {
// 1. 验证数据
// 2. 批量插入
// 3. 更新统计信息
// 要么全部成功,要么全部失败
}
二、🏛️ 事务的四大特性(ACID)
💡 记忆技巧 :ACID = A tomicity, C onsistency, I solation, Durability
事务的四大特性是面试必考点,也是理解事务的基础!
2.1 原子性(Atomicity)⚛️
🔍 定义:事务是一个不可分割的工作单位,事务中的操作要么全部完成,要么全部不完成。
🎭 通俗解释:就像化学中的原子,不能再分割!
java
/**
* 原子性示例:转账操作
* 要么两个操作都成功,要么都失败
*/
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 第1步:扣款
accountMapper.deduct(fromId, amount);
// 模拟异常:如果这里出错,第1步也会回滚
if (amount.compareTo(new BigDecimal(10000)) > 0) {
throw new RuntimeException("转账金额过大!");
}
// 第2步:加款
accountMapper.add(toId, amount);
// 结果:要么fromId-amount且toId+amount,要么都不变
}
📊 原子性保证机制:
全部成功
任一失败
开始事务
执行操作
提交事务
回滚事务
操作生效
恢复到初始状态
2.2 一致性(Consistency)🎯
🔍 定义:事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。
🎭 通俗解释:业务规则不会被破坏!比如转账前后,总金额不变。
java
/**
* 一致性示例:库存扣减
* 确保库存不会变成负数
*/
@Transactional
public void deductStock(Long productId, Integer quantity) {
// 查询当前库存
Product product = productMapper.selectById(productId);
// 业务规则检查:库存必须充足
if (product.getStock() < quantity) {
throw new BusinessException("库存不足!");
}
// 扣减库存
product.setStock(product.getStock() - quantity);
productMapper.updateById(product);
// 一致性保证:库存永远 >= 0
}
🌟 一致性约束示例:
| 🎯 业务场景 | 🔐 一致性规则 |
|---|---|
| 银行转账 | 转账前后总金额不变 |
| 订单创建 | 订单金额 = 商品总价 |
| 库存管理 | 库存数量 >= 0 |
| 账户余额 | 余额 >= 0(除非允许透支) |
2.3 隔离性(Isolation)🔒
🔍 定义:多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
🎭 通俗解释:就像考试时每个学生的答卷互不影响!
java
/**
* 隔离性示例:并发扣减库存
* 多个用户同时购买,互不影响
*/
public class StockService {
// ❌ 没有隔离性的问题代码
public void deductStockWrong(Long productId, Integer quantity) {
// 线程1读取:库存=10
Integer stock = getStock(productId);
// 线程2也读取:库存=10(脏读)
// 线程1扣减:10-5=5
updateStock(productId, stock - quantity);
// 线程2扣减:10-5=5(数据丢失)
// 实际应该是10-5-5=0
}
// ✅ 正确的隔离性保护
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void deductStockCorrect(Long productId, Integer quantity) {
// 使用数据库锁或乐观锁保证隔离
Product product = productMapper.selectByIdForUpdate(productId);
product.setStock(product.getStock() - quantity);
productMapper.updateById(product);
}
}
📊 隔离级别对比:
| 🔐 隔离级别 | 🚫 脏读 | 🚫 不可重复读 | 🚫 幻读 | 📊 性能 | 💡 使用场景 |
|---|---|---|---|---|---|
| READ_UNCOMMITTED 读未提交 | ❌ 可能 | ❌ 可能 | ❌ 可能 | ⭐⭐⭐⭐⭐ | 几乎不用 |
| READ_COMMITTED 读已提交 | ✅ 避免 | ❌ 可能 | ❌ 可能 | ⭐⭐⭐⭐ | Oracle默认 |
| REPEATABLE_READ 可重复读 | ✅ 避免 | ✅ 避免 | ❌ 可能 | ⭐⭐⭐ | MySQL默认 |
| SERIALIZABLE 串行化 | ✅ 避免 | ✅ 避免 | ✅ 避免 | ⭐ | 极高要求 |
🔍 三种并发问题详解:
java
/**
* 并发问题演示类
*/
public class ConcurrencyProblemsDemo {
// 1️⃣ 脏读(Dirty Read)
// 事务A读取了事务B未提交的数据,但事务B最后回滚了
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void dirtyReadExample() {
// 线程A:读取账户余额 = 1000
// 线程B:修改余额为500(未提交)
// 线程A:读取账户余额 = 500(脏读!)
// 线程B:回滚,余额恢复为1000
// 线程A使用了错误的数据500
}
// 2️⃣ 不可重复读(Non-Repeatable Read)
// 事务A多次读取同一数据,但数据被事务B修改了
@Transactional(isolation = Isolation.READ_COMMITTED)
public void nonRepeatableReadExample() {
// 时刻1:读取账户余额 = 1000
BigDecimal balance1 = accountMapper.getBalance(id);
// 此时另一个事务修改了余额为500并提交
// 时刻2:再次读取账户余额 = 500
BigDecimal balance2 = accountMapper.getBalance(id);
// 问题:两次读取结果不一致!
}
// 3️⃣ 幻读(Phantom Read)
// 事务A查询一个范围的数据,事务B插入了新数据
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void phantomReadExample() {
// 第1次查询:查到5条记录
List<User> users1 = userMapper.selectByAge(20);
// 此时另一个事务插入了一条age=20的记录并提交
// 第2次查询:查到6条记录(幻读!)
List<User> users2 = userMapper.selectByAge(20);
// 问题:出现了"幻影"数据
}
}
2.4 持久性(Durability)💾
🔍 定义:事务一旦提交,其对数据库的修改是永久性的,即使系统故障也不会丢失。
🎭 通俗解释:就像你保存了Word文档,即使电脑关机重启,文档还在!
java
/**
* 持久性示例:订单支付
* 一旦支付成功,订单状态永久改变
*/
@Transactional
public void payOrder(Long orderId) {
// 1. 查询订单
Order order = orderMapper.selectById(orderId);
// 2. 更新订单状态为已支付
order.setStatus(OrderStatus.PAID);
order.setPayTime(new Date());
orderMapper.updateById(order);
// 3. 事务提交后,即使系统崩溃,订单状态也不会回退到未支付
// 这就是持久性!
}
🛡️ 持久性保证机制:
- Write-Ahead Logging (WAL):先写日志再写数据
- 事务日志(Redo Log):记录所有修改操作
- 检查点机制:定期将内存数据刷新到磁盘
- 崩溃恢复:系统重启后根据日志恢复数据
三、🌟 Spring事务管理机制
3.1 Spring事务管理概述
Spring提供了统一的事务管理抽象,支持多种事务管理方式:
Spring事务管理
编程式事务
声明式事务
TransactionTemplate
PlatformTransactionManager
XML配置
注解配置
🌟 为什么使用Spring事务管理?
- ✅ 统一的API:不用关心底层是JDBC、Hibernate还是JPA
- ✅ 解耦:事务管理代码与业务代码分离
- ✅ 简化开发:使用注解即可,无需手动管理事务
- ✅ 灵活配置:支持多种传播行为和隔离级别
3.2 Spring事务核心组件
java
/**
* Spring事务核心接口
*/
// 1️⃣ PlatformTransactionManager - 事务管理器接口
public interface PlatformTransactionManager {
// 获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
// 提交事务
void commit(TransactionStatus status);
// 回滚事务
void rollback(TransactionStatus status);
}
// 2️⃣ TransactionDefinition - 事务定义
public interface TransactionDefinition {
// 获取传播行为
int getPropagationBehavior();
// 获取隔离级别
int getIsolationLevel();
// 获取超时时间
int getTimeout();
// 是否只读
boolean isReadOnly();
}
// 3️⃣ TransactionStatus - 事务状态
public interface TransactionStatus {
// 是否是新事务
boolean isNewTransaction();
// 设置回滚标记
void setRollbackOnly();
// 是否已回滚
boolean isRollbackOnly();
// 是否已完成
boolean isCompleted();
}
📊 常用事务管理器:
| 🔧 事务管理器 | 🎯 使用场景 | 📚 依赖技术 |
|---|---|---|
| DataSourceTransactionManager | 使用JDBC或MyBatis | DataSource |
| HibernateTransactionManager | 使用Hibernate | SessionFactory |
| JpaTransactionManager | 使用JPA | EntityManagerFactory |
| JtaTransactionManager | 分布式事务 | JTA |
四、📝 Spring事务的XML配置方式
4.1 基础配置
💡 适用场景:需要统一配置多个类的事务,或者不想在代码中加注解
xml
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1️⃣ 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 2️⃣ 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 3️⃣ 配置事务通知(定义事务的属性) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
配置事务属性:
name: 方法名匹配模式,支持通配符*
propagation: 传播行为
isolation: 隔离级别
timeout: 超时时间(秒)
read-only: 是否只读
rollback-for: 遇到哪些异常回滚
-->
<!-- 查询方法:只读事务 -->
<tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
<!-- 修改方法:读写事务 -->
<tx:method name="save*" propagation="REQUIRED"
isolation="DEFAULT" timeout="30"
rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<!-- 其他方法:默认事务 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 4️⃣ 配置AOP切面(定义事务作用范围) -->
<aop:config>
<!-- 定义切入点:service包下的所有类的所有方法 -->
<aop:pointcut id="txPointcut"
expression="execution(* com.example.service..*.*(..))"/>
<!-- 关联事务通知和切入点 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
4.2 XML配置详解
🔍 事务属性详细说明:
xml
<tx:method
name="方法名"
propagation="传播行为"
isolation="隔离级别"
timeout="超时时间"
read-only="是否只读"
rollback-for="回滚异常类"
no-rollback-for="不回滚异常类"/>
📊 属性说明表:
| 🔧 属性 | 💡 说明 | 📝 示例值 |
|---|---|---|
name |
方法名模式(支持*通配) | save*, update* |
propagation |
事务传播行为 | REQUIRED, REQUIRES_NEW |
isolation |
隔离级别 | DEFAULT, READ_COMMITTED |
timeout |
超时时间(秒,-1表示永不超时) | 30, -1 |
read-only |
是否只读(优化性能) | true, false |
rollback-for |
遇到哪些异常回滚 | Exception.class |
no-rollback-for |
遇到哪些异常不回滚 | IOException.class |
4.3 实际使用示例
java
/**
* 使用XML配置的Service类
* 注意:不需要@Transactional注解
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private AccountMapper accountMapper;
/**
* 保存用户(自动应用事务)
* 匹配XML中的 <tx:method name="save*"/>
*/
public void saveUser(User user) {
// 1. 保存用户信息
userMapper.insert(user);
// 2. 初始化用户账户
Account account = new Account();
account.setUserId(user.getId());
account.setBalance(BigDecimal.ZERO);
accountMapper.insert(account);
// 如果任何步骤失败,整个事务自动回滚
}
/**
* 查询用户(只读事务)
* 匹配XML中的 <tx:method name="get*" read-only="true"/>
*/
public User getUser(Long id) {
return userMapper.selectById(id);
}
}
五、✨ Spring事务的注解配置方式(推荐)
💡 推荐原因:简单、直观、易维护,是现代Spring应用的首选!
5.1 启用注解事务
方式1:XML配置启用
xml
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="...">
<!-- 配置数据源和事务管理器(同上) -->
<!-- 启用注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
方式2:Java配置启用(Spring Boot常用)
java
/**
* Spring Boot配置类
*/
@Configuration
@EnableTransactionManagement // 启用注解事务管理
public class TransactionConfig {
/**
* 配置事务管理器
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
5.2 @Transactional注解详解
🔍 注解位置:可以放在类上或方法上
java
/**
* @Transactional 注解完整示例
*/
@Service
@Transactional // 类级别:所有public方法都应用此事务配置
public class OrderService {
/**
* 方法级别的@Transactional会覆盖类级别的配置
*
* @Transactional参数说明:
* - propagation: 事务传播行为
* - isolation: 隔离级别
* - timeout: 超时时间
* - readOnly: 是否只读
* - rollbackFor: 遇到哪些异常回滚
* - noRollbackFor: 遇到哪些异常不回滚
*/
@Transactional(
propagation = Propagation.REQUIRED, // 必须在事务中运行
isolation = Isolation.REPEATABLE_READ, // 可重复读隔离级别
timeout = 30, // 30秒超时
readOnly = false, // 可读写
rollbackFor = Exception.class, // 遇到任何Exception都回滚
noRollbackFor = BusinessException.class // 业务异常不回滚
)
public void createOrder(Order order) {
// 事务开始...
// 1. 扣减库存
productService.deductStock(order.getProductId(), order.getQuantity());
// 2. 创建订单
orderMapper.insert(order);
// 3. 扣减用户积分
userService.deductPoints(order.getUserId(), order.getPoints());
// 事务提交或回滚
}
/**
* 只读事务:优化性能
*/
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderMapper.selectById(id);
}
}
5.3 @Transactional注解常见配置
java
/**
* 注解配置最佳实践
*/
@Service
public class TransactionExamples {
// ✅ 推荐:标准的写操作事务
@Transactional(rollbackFor = Exception.class)
public void saveData(Data data) {
// 保存数据
}
// ✅ 推荐:只读事务(提高性能)
@Transactional(readOnly = true)
public List<Data> queryData() {
// 查询数据
}
// ✅ 推荐:需要新事务(不受外层事务影响)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) {
// 保存日志,即使外层事务回滚,日志也保存
}
// ⚠️ 注意:设置超时时间(防止长时间锁定)
@Transactional(timeout = 10)
public void batchProcess(List<Data> dataList) {
// 批量处理,10秒后超时
}
// ⚠️ 注意:指定特定异常才回滚
@Transactional(rollbackFor = {SQLException.class, IOException.class})
public void complexOperation() {
// 复杂操作
}
}
六、🔄 事务传播行为详解
💡 核心问题:当一个事务方法调用另一个事务方法时,应该如何处理事务?
6.1 七种传播行为
| 🔖 传播行为 | 📝 说明 | 💡 使用场景 |
|---|---|---|
| REQUIRED (默认) | 有事务就加入,没有就新建 | 最常用的方式 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 | 独立的子事务 |
| SUPPORTS | 有事务就加入,没有就以非事务方式运行 | 查询操作 |
| NOT_SUPPORTED | 以非事务方式运行,挂起当前事务 | 不需要事务的操作 |
| MANDATORY | 必须在事务中运行,否则抛异常 | 强制事务约束 |
| NEVER | 不能在事务中运行,否则抛异常 | 禁止事务 |
| NESTED | 嵌套事务,外层回滚内层也回滚 | 部分回滚场景 |
6.2 REQUIRED传播行为(最常用)
java
/**
* REQUIRED传播行为示例
* 如果当前有事务就加入,没有就新建
*/
@Service
public class PropagationRequiredDemo {
@Autowired
private OrderMapper orderMapper;
@Autowired
private LogService logService;
/**
* 外层方法:创建事务A
*/
@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
System.out.println("外层方法开始");
// 保存订单
Order order = new Order();
orderMapper.insert(order);
// 调用内层方法
logService.innerMethod(); // 会加入到事务A中
System.out.println("外层方法结束");
}
}
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
/**
* 内层方法:也是REQUIRED
* 会加入到外层的事务A中
*/
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
System.out.println("内层方法执行");
// 保存日志
Log log = new Log();
logMapper.insert(log);
// 如果这里抛异常,外层和内层的操作都会回滚
}
}
/**
* 执行流程:
* 1. outerMethod开始 -> 创建事务A
* 2. 插入订单 -> 在事务A中
* 3. innerMethod开始 -> 加入事务A(不创建新事务)
* 4. 插入日志 -> 在事务A中
* 5. innerMethod结束 -> 不提交(因为不是事务创建者)
* 6. outerMethod结束 -> 提交事务A
*
* 结果:订单和日志在同一个事务中,要么都成功,要么都失败
*/
6.3 REQUIRES_NEW传播行为
java
/**
* REQUIRES_NEW传播行为示例
* 总是创建新事务,挂起当前事务
*/
@Service
public class PropagationRequiresNewDemo {
@Autowired
private AccountService accountService;
/**
* 外层方法:事务A
*/
@Transactional(propagation = Propagation.REQUIRED)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
System.out.println("开始转账");
// 1. 扣款(事务A)
accountMapper.deduct(fromId, amount);
// 2. 记录日志(新事务B)
accountService.saveTransferLog(fromId, toId, amount);
// 3. 加款(事务A)
accountMapper.add(toId, amount);
// 模拟异常
if (amount.compareTo(new BigDecimal(10000)) > 0) {
throw new RuntimeException("转账金额过大!");
}
System.out.println("转账完成");
}
}
@Service
public class AccountService {
@Autowired
private LogMapper logMapper;
/**
* REQUIRES_NEW:创建新事务B
* 即使外层事务A回滚,这里的日志也会保存!
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveTransferLog(Long fromId, Long toId, BigDecimal amount) {
Log log = new Log();
log.setContent("从账户" + fromId + "向" + toId + "转账" + amount);
logMapper.insert(log);
// 这个方法结束时,事务B立即提交
}
}
/**
* 执行结果:
* - 如果转账成功:订单、日志都保存
* - 如果转账失败:订单回滚,但日志仍然保存!
*
* 应用场景:
* 1. 操作日志记录(即使业务失败也要记录)
* 2. 审计追踪
* 3. 消息发送记录
*/
6.4 NESTED传播行为
java
/**
* NESTED传播行为示例
* 嵌套事务:外层回滚内层也回滚,但内层回滚不影响外层
*/
@Service
public class PropagationNestedDemo {
@Autowired
private RewardService rewardService;
/**
* 外层方法:创建订单
*/
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 1. 保存订单(必须成功)
orderMapper.insert(order);
try {
// 2. 发放优惠券(可以失败)
rewardService.giveCoupon(order.getUserId());
} catch (Exception e) {
// 捕获异常,不影响订单创建
log.error("发放优惠券失败", e);
}
// 3. 发送通知
notificationService.sendOrderNotification(order);
}
}
@Service
public class RewardService {
/**
* NESTED:嵌套事务
* 如果失败,只回滚优惠券操作,不影响订单
*/
@Transactional(propagation = Propagation.NESTED)
public void giveCoupon(Long userId) {
// 发放优惠券
couponMapper.insert(new Coupon(userId));
// 如果这里抛异常
// 1. 只回滚giveCoupon方法的操作
// 2. 不影响createOrder中的订单保存
// 3. 前提是外层捕获了异常
}
}