spring事务——try{...}catch{...}中事务不回滚的几种处理方式

本文深入探讨了在方法中使用@Transactional注解实现事务管理的细节,包括如何确保在不同类型的异常下触发事务回滚,以及在捕获异常时的正确处理方式,提供了多种解决方案。

当希望在某个方法中添加事务时,我们常常在方法头上添加@Transactional注解

@ResponseBody
@RequestMapping(value = "/payment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
public Payment paymentJson(@RequestBody PaymentRequestInfo entity) {
  //method
}

容易让人忽略的是:方法上未加任何属性的@Transactional注解只能在抛出RuntimeException或者Error时才会触发事务的回滚,常见的非RuntimeException是不会触发事务的回滚的。

如果要在抛出 非RuntimeException时也触发回滚机制,需要我们在注解上添加 rollbackFor = { Exception.class }属性。

@ResponseBody
@RequestMapping(value = "/payment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional(rollbackFor = { Exception.class })
public Payment paymentJson(@RequestBody PaymentRequestInfo entity) {
    //method
}

当然,上面事务回滚的前提是添加@Transactional注解的方法中不含有try{...}catch{...}捕获异常,使得程序运行过程中出现异常能顺利抛出,从而触发事务回滚。

在实际开发中,我们往往需要在方法中进行异常的捕获,从而对异常进行判断,为客户端返回提示信息。但是此时由于异常的被捕获,导致事务的回滚没有被触发,导致事务的失败。

下面提供几种解决方法:

1. 使用@Transactional注解,抛出@Transactional注解默认识别的RuntimeException

方法上使用@Transactional注解,在捕获到异常时在catch语句中抛出RuntimeException。

2. 使用@Transactional(rollbackFor = { Exception.class }),抛出捕获的非RuntimeException异常

方法上使用@Transactional(rollbackFor = { Exception.class })注解声明事务回滚级别,在捕获到异常时在catch语句中直接抛出所捕获的异常。

3. 手动回滚

上面两个在catch{...}中抛出异常的方法都有个不足之处,就是不能在catch{...}中存在return子句,所以设置手动回滚,当捕获到异常时,手动回滚,同时返回前台提示信息。

 

在使用 `CompletableFuture.runAsync` 进行异步任务处理时,确保事务回滚的关键在于如何正确管理事务边界以及线程上下文。通常情况下,Spring 框架中的 `@Transactional` 注解依赖于线程绑定的事务管理机制。然而,在异步方法中,由于任务运行在同的线程上,导致主线程的事务上下文无法传递到子线程,从而使得事务失效[^1]。 ### 使用编程式事务管理 为了克服这个问题,可以采用编程式事务管理来替代声明式的 `@Transactional` 注解。通过 `TransactionTemplate` 或者 `PlatformTransactionManager` 手动控制事务的开始、提交和回滚过程。这样可以在异步任务中明确地开启一个新的事务,并且独立于主线程的事务上下文。 ```java @Service public class MyService { @Autowired private MyRepository myRepository; @Autowired private PlatformTransactionManager transactionManager; public void performTransactionalOperation(String data) { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { myRepository.save(data); if (someCondition()) { throw new RuntimeException("Something went wrong"); } transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw e; } } private boolean someCondition() { // 实现条件判断逻辑 return true; // 示例返回值 } } ``` 然后,在调用 `CompletableFuture.runAsync` 时,将需要执行的操作封装进一个 `Runnable` 对象,并确保它被提交给配置了适当事务属性的任务执行器。 ```java @Test void testAsyncTransaction() throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); // 或者使用自定义线程池 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { ((MyService) AopContext.currentProxy()).performTransactionalOperation("Async Data"); }, executor); future.get(); // 阻塞直到异步操作完成 executor.shutdown(); } ``` 这里需要注意的是,当从 `CompletableFuture.runAsync` 内部调用服务层方法时,如果希望保持代理对象的有效性(以便应用切面如安全检查、日志记录等),可能需要通过 `AopContext.currentProxy()` 获取当前 AOP 代理实例。 ### 利用 ThreadLocal 存储事务信息 另一种解决方案是利用 `ThreadLocal` 来存储事务相关信息,使得即使跨线程也能访问相同的事务上下文。过这种方法较为复杂,因为它要求手动传播事务状态,并且容易出错。因此推荐优先考虑使用编程式事务管理。 ### 异常处理与回滚策略 对于异常处理,应当捕获所有必要的异常并在 catch 块中显式调用 `rollback` 方法以触发事务回滚。此外,还可以配置全局异常处理器或者使用 `@ExceptionHandler` 来统一处理特定类型的异常并执行相应的回滚动作。 ### 相关问题 1. 如何在 Spring Boot 应用程序中实现多数据源下的分布式事务? 2. 在非 Spring 管理的类中怎样获取到 PlatformTransactionManager 实例? 3. 如果想要在异步方法结束后立即提交事务是等待整个 future 完成,应该怎么做? 4. 除了使用 get() 方法阻塞当前线程外,还有哪些方式能够有效地处理 CompletableFuture 的结果而影响主流程? 以上措施可以帮助开发者更好地理解和解决在异步编程模型下遇到的事务一致性挑战。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值