spring事务失效问题可以这样解决

今天咱们就来聊聊在Spring事务管理中你可能踩过的坑,并教你如何规避这些陷阱。

事务提交的陷阱

有时,事务方法在抛出异常后没有回滚,而是被提交了。

这通常是由于异常被捕获但没有显式抛出,导致Spring误以为事务正常完成。

错误示例
复制代码

java

@Transactional
public void processTransaction() {
try {
// 执行一些数据库操作
userRepository.save(new User("John"));

// 模拟异常
if (true) {
throw new RuntimeException("模拟异常");
}
} catch (Exception e) {
// 捕获异常,但没有显式抛出,事务可能不会回滚
System.out.println("处理异常:" + e.getMessage());
}
}

修正后的代码
复制代码

java

@Transactional(rollbackFor = Exception.class) // 指定回滚的异常类型
public void processTransaction() throws Exception {
// 执行一些数据库操作
userRepository.save(new User("John"));

// 模拟异常
if (true) {
throw new Exception("强制回滚");
}
}

事务不回滚

在事务方法中处理业务逻辑时,异常可能会被捕获和处理,导致事务没有正确回滚。

错误示例
复制代码

java

@Transactional
public void processData() {
try {
// 进行数据库操作
userRepository.updateUser(new User("Jane"));

// 模拟一个业务异常
throw new CustomBusinessException("业务错误");
} catch (CustomBusinessException e) {
// 捕获异常,事务未回滚
System.out.println("捕获业务异常:" + e.getMessage());
}
}

问题分析

  • 在捕获异常后,事务没有被标记为回滚,因此 processData 方法中的数据库操作仍然会提交,导致数据状态不一致。
修正后的代码
复制代码

java

@Transactional(rollbackFor = CustomBusinessException.class) // 指定业务异常时回滚
public void processData() throws CustomBusinessException {
// 进行数据库操作
userRepository.updateUser(new User("Jane"));

// 模拟一个业务异常
throw new CustomBusinessException("业务错误");
}

死锁的困境与破解

死锁是数据库操作中常见的问题,尤其是在多个事务相互依赖资源时。

通常表现在两个或多个事务相互等待对方释放锁,结果大家都无法完成。

以下是一个可能发生死锁的简单示例:

复制代码

java

@Transactional
public void updateUserBalances(Long user1Id, Long user2Id) {
User user1 = userRepository.findById(user1Id);
User user2 = userRepository.findById(user2Id);

user1.setBalance(user1.getBalance() - 100);
user2.setBalance(user2.getBalance() + 100);

userRepository.save(user1);
userRepository.save(user2);
}

为了避免死锁,我们需要确保事务方法锁定资源的顺序一致,或者通过短小的事务方法减少锁持有时间。

此外,合理设计事务逻辑,避免循环依赖也很重要。

复制代码

java

@Transactional
public void updateUserBalancesConsistently(Long user1Id, Long user2Id) {
User user1 = userRepository.findById(user1Id);
User user2 = userRepository.findById(user2Id);

// 按照固定的顺序处理用户更新,避免死锁
if (user1Id < user2Id) {
updateBalances(user1, user2);
} else {
updateBalances(user2, user1);
}
}

private void updateBalances(User user1, User user2) {
user1.setBalance(user1.getBalance() - 100);
user2.setBalance(user2.getBalance() + 100);

userRepository.save(user1);
userRepository.save(user2);
}

事务提交后的回调陷阱

有时候,我们会在事务提交后触发一些回调事件,比如发送消息或者更新缓存。

但如果在这些回调中重新开启事务,可能会导致Spring的事务状态和数据库的事务状态不一致,进而引发意想不到的错误。

示例代码
复制代码

java

@Transactional
public void processOrder() {
// 一些订单处理逻辑
transactionSynchronizationRegistry.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 回调事件中开启新事务
processNotification();
}
});
}

public void processNotification() {
// 这里我们可能再次使用事务来处理通知逻辑
// 但如果事务状态管理不当,可能导致问题
notificationService.sendNotification();
}

修正后的代码

为了避免这种问题,可以使用 Propagation.REQUIRES_NEW 来确保在回调中开启的新事务与原事务分离,或在回调前清除事务状态。

复制代码

java

@Transactional
public void processOrder() {
// 订单处理逻辑
transactionSynchronizationRegistry.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 使用新的事务传播行为
notificationService.sendNotificationInNewTransaction();
}
});
}

@Service
public class NotificationService {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotificationInNewTransaction() {
// 在新的事务中处理通知发送逻辑
sendNotification();
}

public void sendNotification() {
// 实际的通知发送逻辑
System.out.println("Notification sent.");
}
}

事务状态管理工具类

在某些复杂的业务场景中,我们可能需要手动管理事务的状态。

此时,创建一个事务管理工具类可以帮助我们更加灵活地控制事务的开启、提交和回滚。

工具类实现

以下是一个简单的事务状态管理工具类的实现示例,在需要时手动管理事务的生命周期。

复制代码

java

@Component
public class TransactionManager {

@Autowired
private PlatformTransactionManager transactionManager;

/**
`

  • 在新事务中执行任务
  • @param task 要执行的任务
    */
    public void executeInNewTransaction(Runnable task) {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    // 设置事务传播行为为 PROPAGATION_REQUIRES_NEW
    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    // 执行任务并在新事务中提交
    transactionTemplate.execute(status -> {
    task.run();
    return null;
    });
    }
    }`
使用示例
复制代码

java

@Service
public class OrderService {

@Autowired
private TransactionManager transactionManager;

public void placeOrder(Order order) {
// 在主事务中处理订单保存
saveOrder(order);

// 使用事务管理工具类在新事务中发送通知
transactionManager.executeInNewTransaction(() -> {
notificationService.sendOrderConfirmation(order);
});
}

@Transactional
public void saveOrder(Order order) {
// 保存订单逻辑
orderRepository.save(order);
}
}

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

相关推荐
我是一颗柠檬13 小时前
【MySQL全面教学】MySQL性能优化实战Day13(2026年)
数据库·后端·sql·mysql·性能优化·database
辰海Coding13 小时前
MiniSpring框架学习-分解 Dispatcher
java·学习·spring·架构
AI人工智能+电脑小能手13 小时前
【大白话说Java面试题 第84题】【Mysql篇】第14题:为什么用 InnoDB 存储引擎的表建议用整型的自增主键?
java·开发语言·数据库·mysql·面试
小江的记录本14 小时前
【JVM虚拟机】JVM调优:常用JVM参数、调优核心指标、OOM排查、GC日志分析、Arthas工具使用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
张彦峰ZYF14 小时前
检索增强生成(RAG)系统的基础:全面深入矢量数据库
数据库·大模型·rag
J2虾虾14 小时前
Spring AI Alibaba文档
java·人工智能·spring
Elastic 中国社区官方博客14 小时前
我们如何在 Elasticsearch Serverless 上将向量搜索吞吐量提升一倍
大数据·数据库·人工智能·elasticsearch·搜索引擎·云原生·serverless
一 乐15 小时前
高校实习信息发布网站|基于Spring Boot的高校实习信息发布网站的设计与实现(源码+数据库+文档)
java·数据库·spring boot·后端·论文·毕设·高校实习信息发布网站
weelinking15 小时前
【产品】11_实现后端接口——数据在背后如何流动
java·人工智能·python·sql·oracle·json·ai编程
zgl_2005377915 小时前
源代码:跨数据库通用SQL语法解析与标注拆解
大数据·数据库·数据仓库·sql·etl·源代码管理