一、事务的本质:数据库操作的"原子性保证"
想象你正在银行进行转账操作:
-- 从A账户扣除1000元
UPDATE accounts SET balance = balance - 1000 WHERE id = 'A';
-- 向B账户增加1000元
UPDATE accounts SET balance = balance + 1000 WHERE id = 'B';
关键问题:如果第一条SQL执行后系统崩溃,会发生什么?
- A账户被扣款,但B账户未收到 → 数据不一致
事务解决方案:
START TRANSACTION; -- 开启事务
UPDATE accounts SET balance = balance - 1000 WHERE id = 'A';
UPDATE accounts SET balance = balance + 1000 WHERE id = 'B';
COMMIT; -- 提交事务(或ROLLBACK回滚)
事务确保:要么所有操作都成功,要么全部失败回滚
二、Spring事务核心概念
1. 事务四大特性(ACID)
特性 | 说明 | 示例 |
---|---|---|
Atomicity(原子性) | 事务是不可分割的最小单元 | 转账操作要么全执行,要么全不执行 |
Consistency(一致性) | 事务前后数据状态一致 | 转账前后总金额不变 |
Isolation(隔离性) | 并发事务互不干扰 | 转账时其他查询不受影响 |
Durability(持久性) | 事务提交后永久生效 | 转账成功后数据永久保存 |
2. Spring事务管理关键接口
public interface PlatformTransactionManager {
// 获取事务状态
TransactionStatus getTransaction(TransactionDefinition definition);
// 提交事务
void commit(TransactionStatus status);
// 回滚事务
void rollback(TransactionStatus status);
}
public interface TransactionDefinition {
// 传播行为
int getPropagationBehavior();
// 隔离级别
int getIsolationLevel();
// 超时时间
int getTimeout();
// 是否只读
boolean isReadOnly();
}
三、事务传播机制详解
1. 为什么需要传播机制?
考虑电商下单场景:
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 保存订单
orderDao.save(order);
// 扣减库存
inventoryService.deductStock(order);
// 增加积分
pointsService.addPoints(order.getUserId(), order.getAmount());
}
}
@Service
public class InventoryService {
@Transactional
public void deductStock(Order order) {
// 扣减库存逻辑
}
}
关键问题:
- 当
createOrder
调用deductStock
时,应该使用同一个事务还是独立事务? - 如果库存扣减失败,订单是否应该回滚?
- 传播机制定义了事务方法相互调用时的行为规则
2. 七种传播行为详解
(1) REQUIRED(默认):有则加入,无则新建
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 事务操作...
methodB(); // 调用B方法
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 事务操作...
}
行为分析:
- 如果methodA有事务:methodB加入该事务
- 如果methodA无事务:methodB新建事务
适用场景:大多数业务场景(如订单创建流程)
(2) SUPPORTS:有则加入,无则非事务
@Transactional(propagation = Propagation.SUPPORTS)
public void getProductInfo(Long id) {
// 查询操作
}
行为分析:
- 调用方有事务:加入事务
- 调用方无事务:非事务执行
适用场景:查询操作(可读性优先)
(3) MANDATORY:必须有事务,否则抛异常
@Transactional(propagation = Propagation.MANDATORY)
public void updatePrice(Product product) {
// 更新价格
}
适用场景:必须保证事务性的操作(如资金变动)
(4) REQUIRES_NEW:新建独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation(String action) {
// 记录操作日志
}
行为分析:
- 无论调用方是否有事务,都新建事务
- 挂起调用方事务(如果存在)
适用场景:日志记录、非核心操作(失败不影响主流程)
(5) NOT_SUPPORTED:非事务执行
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendNotification(User user) {
// 发送通知(无需事务)
}
行为分析:
- 挂起调用方事务(如果存在)
- 非事务执行当前方法
适用场景:发送消息、通知等非关键操作
(6) NEVER:禁止事务,否则抛异常
@Transactional(propagation = Propagation.NEVER)
public void getCacheData(String key) {
// 获取缓存数据
}
行为分析:
- 调用方有事务:抛出
IllegalTransactionStateException
- 调用方无事务:正常执行
适用场景:缓存操作等不需要事务的场景
(7) NESTED:嵌套事务
@Transactional(propagation = Propagation.NESTED)
public void updateInventory(Order order) {
// 更新库存
}
行为分析:
- 调用方有事务:创建嵌套事务(保存点)
- 调用方无事务:新建事务(同REQUIRED)
特点:
- 嵌套事务回滚不影响外部事务
- 外部事务回滚会导致嵌套事务回滚
适用场景:部分可独立失败的操作(如库存扣减)
3. 传播行为对比表
传播行为 | 当前有事务 | 当前无事务 | 异常影响范围 | 适用场景 |
---|---|---|---|---|
REQUIRED | 加入 | 新建 | 全部回滚 | 通用业务 |
SUPPORTS | 加入 | 非事务 | 部分回滚 | 查询操作 |
MANDATORY | 加入 | 抛异常 | 全部回滚 | 关键操作 |
REQUIRES_NEW | 新建 | 新建 | 独立回滚 | 日志记录 |
NOT_SUPPORTED | 非事务 | 非事务 | 无影响 | 通知发送 |
NEVER | 抛异常 | 非事务 | 无影响 | 缓存操作 |
NESTED | 嵌套事务 | 新建 | 部分回滚 | 库存扣减 |
四、传播机制实战案例
案例1:订单创建(REQUIRED + REQUIRES_NEW)
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
try {
// 保存订单
orderDao.save(order);
// 记录日志(独立事务)
logService.log("订单创建", order.getId());
} catch (Exception e) {
// 订单保存失败会回滚,但日志已独立提交
throw new OrderException("订单创建失败");
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String action, Long orderId) {
// 记录操作日志(即使订单失败,日志仍保留)
}
}
案例2:用户注册(NESTED事务)
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void register(User user) {
// 保存用户
userDao.save(user);
try {
// 初始化账户(嵌套事务)
accountService.initAccount(user.getId());
} catch (Exception e) {
// 账户初始化失败,但用户注册仍成功
log.error("账户初始化失败", e);
}
// 发送欢迎邮件(非事务)
emailService.sendWelcomeEmail(user.getEmail());
}
}
@Service
public class AccountService {
@Transactional(propagation = Propagation.NESTED)
public void initAccount(Long userId) {
// 初始化用户账户(可独立失败)
}
}
五、隔离级别与传播机制协同
1. 隔离级别回顾
隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
---|---|---|---|---|
READ_UNCOMMITTED | ✓ | ✓ | ✓ | 高 |
READ_COMMITTED | ✗ | ✓ | ✓ | 中 |
REPEATABLE_READ | ✗ | ✗ | ✓ | 中低 |
SERIALIZABLE | ✗ | ✗ | ✗ | 低 |
2. 隔离级别与传播行为组合
// 资金转账服务(高隔离级别)
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.REPEATABLE_READ
)
public void transfer(TransferRequest request) {
// 转账核心逻辑
}
// 商品查询服务(低隔离级别)
@Transactional(
propagation = Propagation.SUPPORTS,
isolation = Isolation.READ_COMMITTED
)
public Product getProduct(Long id) {
// 查询商品信息
}
六、Spring事务实现原理
传播行为实现关键代码
public class TransactionManager {
public void execute(TransactionDefinition definition, Runnable task) {
// 1. 获取当前事务状态
TransactionStatus status = getTransaction(definition);
try {
// 2. 执行业务逻辑
task.run();
// 3. 提交事务
commit(status);
} catch (Exception ex) {
// 4. 回滚事务
rollback(status);
throw ex;
}
}
private TransactionStatus getTransaction(TransactionDefinition definition) {
// 传播行为处理
switch (definition.getPropagationBehavior()) {
case REQUIRED:
if (currentTransactionExists()) {
return handleExistingTransaction(definition);
}
return startNewTransaction(definition);
case REQUIRES_NEW:
suspendCurrentTransaction();
return startNewTransaction(definition);
case NESTED:
if (currentTransactionExists()) {
return createNestedTransaction();
}
return startNewTransaction(definition);
// 其他传播行为...
}
}
}
七、常见问题与解决方案
1. 事务不生效的常见原因
问题原因 | 解决方案 |
---|---|
方法非public | 改为public方法 |
自调用问题 | 通过AopContext获取代理 |
异常类型错误 | 配置rollbackFor |
数据库引擎不支持 | 使用InnoDB引擎 |
多数据源未指定 | 明确指定事务管理器 |
2. 自调用问题解决方案
@Service
public class UserService {
// 注入自身代理
@Autowired
private UserService selfProxy;
public void batchUpdate() {
for (User user : users) {
// 通过代理调用保证事务生效
selfProxy.updateUser(user);
}
}
@Transactional
public void updateUser(User user) {
// 更新用户
}
}
3. 正确配置回滚异常
// 默认只回滚RuntimeException
@Transactional(rollbackFor = Exception.class)
public void updateData() throws BusinessException {
// 可能抛出受检异常
}
八、性能优化建议
1. 事务优化策略
策略 | 实现方式 | 效果 |
---|---|---|
缩短事务时间 | 事务内避免远程调用 | 减少锁竞争 |
降低隔离级别 | 使用READ_COMMITTED | 提高并发 |
使用只读事务 | @Transactional(readOnly=true) | MySQL优化 |
合理设置超时 | @Transactional(timeout=30) | 防止死锁 |
2. 批量操作优化
@Transactional
public void batchInsert(List<User> users) {
for (int i = 0; i < users.size(); i++) {
userDao.insert(users.get(i));
// 每100条提交一次
if (i % 100 == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
九、最佳实践总结
1. 事务设计原则
原则 | 说明 | 示例 |
---|---|---|
最小事务原则 | 事务范围尽量小 | 避免在事务中处理业务逻辑 |
明确传播行为 | 显式指定传播类型 | @Transactional(propagation=Propagation.REQUIRED) |
合理设置隔离级别 | 根据业务需求选择 | 查询操作使用READ_COMMITTED |
异常处理一致 | 统一异常回滚策略 | 配置rollbackFor |
2. 事务使用清单
// 1. 在Service层使用事务
@Service
public class OrderService {
// 2. 显式指定传播行为
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 3. 避免在事务中进行远程调用
orderDao.save(order);
// 4. 非核心操作使用REQUIRES_NEW
logService.logOrderCreation(order.getId());
}
// 5. 查询方法使用只读事务
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderDao.findById(id);
}
}
总结:事务传播的本质
Spring事务传播机制的核心是解决事务边界问题,它定义了:
- 事务从哪里开始(创建新事务)
- 事务到哪里结束(提交/回滚)
- 事务之间的关系(加入/独立)
通过合理使用传播行为,我们可以:
- 保证数据一致性
- 提高系统性能
- 增强代码可维护性
- 灵活处理复杂业务场景
根据业务需求选择最合适的传播行为,而不是盲目使用默认值。