Spring 事务失效的八种场景

1. 抛出检查异常导致事务不能正确回滚

@Service
public class Service1 {@Autowiredprivate AccountMapper accountMapper;@Transactionalpublic void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}}
}
  • 原因:Spring 默认只会回滚非检查异常
  • 解法:配置 rollbackFor 属性

@Transactional(rollbackFor = Exception.class)

2. 业务方法内自己 try-catch 异常导致事务不能正确回滚

@Service
public class Service2 {@Autowiredprivate AccountMapper accountMapper;@Transactional(rollbackFor = Exception.class)public void transfer(int from, int to, int amount)  {try {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}} catch (FileNotFoundException e) {e.printStackTrace();}}
}
  • 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
  • 解法1:异常原样抛出:

在 catch 块添加 throw new RuntimeException(e);

解法2:手动设置 TransactionStatus.setRollbackOnly()

在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3. aop 切面顺序导致导致事务不能正确回滚

@Service
public class Service3 {@Autowiredprivate AccountMapper accountMapper;@Transactional(rollbackFor = Exception.class)public void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}}
}
@Aspect
public class MyAspect {@Around("execution(* transfer(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {LoggerUtils.get().debug("log:{}", pjp.getTarget());try {return pjp.proceed();} catch (Throwable e) {e.printStackTrace();return null;}}
}
  • 原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…

  • 解法1、2:同情况2 中的解法:1、2

  • 解法3:调整切面顺序,在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1) (不推荐)

4. 非 public 方法导致的事务失效

@Service
public class Service4 {@Autowiredprivate AccountMapper accountMapper;@Transactionalvoid transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}
}
  • 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的

  • 解法1:改为 public 方法

  • 解法2:添加 bean 配置如下(不推荐)

@Bean
public TransactionAttributeSource transactionAttributeSource() {return new AnnotationTransactionAttributeSource(false);
}

5. 父子容器导致的事务失效

@Service
public class Service5 {@Autowiredprivate AccountMapper accountMapper;@Transactional(rollbackFor = Exception.class)public void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}
}

控制器类:

@Controller
public class AccountController {@Autowiredpublic Service5 service;public void transfer(int from, int to, int amount) throws FileNotFoundException {service.transfer(from, to, amount);}
}

App 配置类

@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {// ... 有事务相关配置
}

Web 配置类

@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {// ... 无事务配置
}

现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效

  • 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来

  • 解法1:各扫描各的,不要图简便

  • 解法2:不要用父子容器,所有 bean 放在同一容器

6. 调用本类方法导致传播行为失效

@Service
public class Service6 {@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() throws FileNotFoundException {LoggerUtils.get().debug("foo");bar();}@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void bar() throws FileNotFoundException {LoggerUtils.get().debug("bar");}
}
  • 原因:本类方法调用不经过代理,因此无法增强
  • 解法1:依赖注入自己(代理)来调用
  • 解法2:通过 AopContext 拿到代理对象,来调用
  • 解法3:通过 CTW,LTW 实现功能增强

解法1:

@Service
public class Service6 {@Autowiredprivate Service6 proxy; // 本质上是一种循环依赖@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() throws FileNotFoundException {LoggerUtils.get().debug("foo");System.out.println(proxy.getClass());proxy.bar();}@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void bar() throws FileNotFoundException {LoggerUtils.get().debug("bar");}
}

解法2:还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)

@Service
public class Service6 {@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() throws FileNotFoundException {LoggerUtils.get().debug("foo");((Service6) AopContext.currentProxy()).bar();}@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void bar() throws FileNotFoundException {LoggerUtils.get().debug("bar");}
}

7. @Transactional 没有保证原子行为

@Service
public class Service7 {private static final Logger logger = LoggerFactory.getLogger(Service7.class);@Autowiredprivate AccountMapper accountMapper;@Transactional(rollbackFor = Exception.class)public void transfer(int from, int to, int amount) {int fromBalance = accountMapper.findBalanceBy(from);logger.debug("更新前查询余额为: {}", fromBalance);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}public int findBalance(int accountNo) {return accountMapper.findBalanceBy(accountNo);}
}

上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况

  • 原因:事务的原子性仅涵盖 insert、update、delete、select … for update 语句,select 方法并不阻塞
    在这里插入图片描述
    如上图所示,红色线程和蓝色线程的查询都发生在扣减之前,都以为自己有足够的余额做扣减

8. @Transactional 方法导致的 synchronized 失效

针对上面的问题,能否在方法上加 synchronized 锁来解决呢?

@Service
public class Service7 {private static final Logger logger = LoggerFactory.getLogger(Service7.class);@Autowiredprivate AccountMapper accountMapper;@Transactional(rollbackFor = Exception.class)public synchronized void transfer(int from, int to, int amount) {int fromBalance = accountMapper.findBalanceBy(from);logger.debug("更新前查询余额为: {}", fromBalance);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}public int findBalance(int accountNo) {return accountMapper.findBalanceBy(accountNo);}
}

答案是不行,原因如下:

  • synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内

  • 可以参考下图发现,蓝色线程的查询只要在红色线程提交之前执行,那么依然会查询到有 1000 足够余额来转账
    在这里插入图片描述

  • 解法1:synchronized 范围应扩大至代理方法调用

  • 解法2:使用 select … for update 替换 select

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/57606.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sketch打不开AI文件?转换方法在这里

1、对比设计软件 Sketch 与 AI 软件功能 Sketch 与 Illustrator 都是行业内优秀的矢量图形设计软件,各有千秋。Sketch 从 2010 年面世,专注 APP 界面设计,深受初学者与专业人士喜爱。Illustrator 拥有更悠久的历史,是处理复杂图标…

Java基础入门篇——Java变量类型的转换和运算符(七)

目录 一、变量类型 1.1自动类型转换(隐式转换) 1.2 强制类型转换(显式转换) 1.3类型转换的其他情况 二、运算符 2.1算术运算符 2.2比较运算符 2.3逻辑运算符 2.4位运算符 三、总结 在Java中,变量类型的转换…

重生学c++系列第三课类和对象(上)

好的我们重生c系列的前两期已经介绍完了c祖师爷针对C语言补充的几个新功能,现在我们进入c的真正课题学习——类与对象: C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 比如说我们洗菜做饭&am…

大数据培训课程-《机器学习从入门到精通》上新啦

《机器学习从入门到精通》课程是一门专业课程,面向人工智能技术服务,课程系统地介绍了Python编程库、分类、回归、无监督学习和模型使用技巧以及算法和案例充分融合。 《机器学习从入门到精通》课程亮点: 课程以任务为导向,逐步学…

【chrome扩展开发】vue-i18n使用问题及解决方案

记录chrome扩展开发时调用vue-i18n的一些问题和解决方法 环境 vue: ^3.3.4vue-i18n: ^9.2.2vite: ^4.4.8 错误1 Uncaught (in promise) EvalError: Refused to evaluate a string as JavaScript because unsafe-eval is not an allowed source of script in the following Con…

C++ 友元

文章目录 前言一、什么是友元二、友元的特性三、示例代码总结 前言 在C编程中,友元(friend)是一种特殊的关系,允许一个类或函数访问另一个类中的私有成员。 一、什么是友元 1.友元 的定义: 友元在C中可以被用于类和…

Jmeter添加cookie的两种方式

jmeter中添加cookie可以通过配置HTTP Cookie Manager,也可以通过HTTP Header Manager,因为cookie是放在头文件里发送的。 实例:博客园点击添加新随笔 https://i.cnblogs.com/EditPosts.aspx?opt1 如果未登录,跳转登录页&#xf…

点成分享丨qPCR仪的原理与使用——以Novacyt产品为例

近年来,PCR检测在多种领域发挥着巨大的作用。短时高效和即时监测都成为了PCR仪发展的方向。作为世界领先的制造商之一,Novacyt公司为来自全球多个国家和行业的用户提供了优质的qPCR仪。 MyGo Mini S qPCR仪是一种紧凑型的实时qPCR仪,非常适合…

git clone 报错Filename too long

1.使用git clone代码,爆出Filename too long错误 2.原因分析 因为我很少看git clone日志,所以从未想过是clone异常,而且也看到代码clone下来了,所以我就显然以为代码clone成功,但是使用idea打开代码后发现大量代码无法…

【图论】单源最短路

算法提高课笔记。(本篇还未更新完… 目录 单源最短路的建图方式例题热浪题意思路代码 信使题意思路代码 香甜的黄油题意思路代码 最小花费题意思路代码 最优乘车题意思路代码 昂贵的聘礼题意思路代码 单源最短路的建图方式 最短路问题可以分为以下两类&#xff1a…

在QT及VS运行包含opencv的cmakelists实例

本文分享如何利用QT和Visual Studio运行cmake组织管理的程序&#xff0c;也就是运行cmakelists.txt。 main和cmakelists内容 main和cmakelists文件路径如下&#xff1a; main.cpp #include<opencv2/opencv.hpp> #include<iostream> #include <string> usi…

Docker容器监控(Cadvisor +Prometheus+Grafana)

环境部署&#xff0c;接着上一篇文章Docker容器部署&#xff08;Cadvisor InfluxDBGrafana&#xff09;开始 目录 1、先清理一下容器 2、部署Cadvisor 3、访问Cadvisor页面 4、部署Prometheus 5、准备配置 6、运行prometheus容器 7、访问prometheus页面 8、部署Grafan…