先别急着回答,看完再说也不迟嘛。我们都知道在 Spring 项目中,我们可以直接使用注解 @Transactional 来标识一个事务方法。然而,你可能并不知道这个事务是不是按照你想的方式执行。下面我们就一起来看看几种情况,你以为的事务管理可能并不是你以为的事务管理。

0 经典错误案例

    @Transactional
void transfer() {
try{
//...
} catch (Exception e){
LOGGER.error(e.getMessage())
}
}

1 抛出检查异常

方法若是抛出检查异常,比如 fileNotFound 这种,事务是不会回滚的,原因也很简单,因为 @Transactional 注解默认的 rollbackFor 是运行时异常。这也就是为什么阿里的开发规范中要求一定要指定 rollbackFor 的原因。

所以我们在使用这个注解的时候还是建议写明 rollbackFor 这样你是明确知道出现了什么异常才会回滚的。

@Transactional(rollbackFor = Exception.class)

2 错误的使用 try catch

业务方法中使用了 try catch 捕获了异常,然后异常顺利出现,结果进入你的 catch 块中,你却没有抛出,结果事务没有正确被回滚。这里出现的原理是 Spring 使用了代理来实现事务管理,调用顺序是开启事务,执行目标方法,提交或回滚事务,虽然你的目标方法出现了异常,架不住你自己处理了,在代理类看来,目标方法没有抛出异常。所以事务也就正常提交。

    @Transactional(rollbackFor = Exception.class)
public void transfer(int amount) {
try{
serviceB.sub(amount);
serviceA.add(amount);
} catch (Exception e) {
LOGGER.error("error occur");
}
}
 

正确做法有两种,一种是在 catch 中继续抛出异常,第二种是告诉 Spring 我的当前事务需要 rollback.

// 1
throw new RuntimeException(e);
// 2
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3 错误添加切面

AOP 切面顺序导致事务不能正确回滚,原因是事务切面优先级最低,但如果自定义的切面优先级和它一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常,在 catch 中吃掉了异常,此时就会出现和第二种情况类似的情况,代理类得不到异常信息,也就不会回滚。

解决方案就是在切面里面也抛出异常,或者是将自定义的切面的优先级设置为更小。但是建议使用第一种在切面中抛出异常,不过话说回来,为什么我们要为一个已经有事务管理的方法添加切面呢…… 事务方法一般在 Service 层,我们可以为 Controller 层添加切面。

4 @Transactional 事务默认只能加在 public 的方法上

非 public 的方法会导致事务失效。Spring 为方法创建代理,添加事务通知,前提条件是该方法是 public 的,这点需要注意的就是要么设置方法为 public,要么设置可以为非 public 的方法添加事务通知。

建议使用 public 方法而不是添加配置

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

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

同一个 Service 的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用是一个代理类的调用,但是我们若是直接在方法中内部调用,不好意思,被调用的方法的事务失效,没有被 AOP 增强。

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public void a (){
b();
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void b (){ }

改进方案就是自己调用自己,自己注入自己。你可能看见过这样的代码,就是为了解决这个问题的。这种解决方案最常见。

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public void a (){
service.b();
}

还有一种方法可以通过 AopContext 拿到代理对象,然后再调用。这里要注意,使用这种方式需要开启暴露代理。

@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void a (){
((TestService)AopContext.currentProxy()).b();
}

关键是这里有一个注释说到不保证 AopContext 一定 work,呃呃呃 好吧,还是自己注入自己吧。

    /**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;

Transactional 并没有保证原子性行为

这个问题非常常见,我们总会以为加了事务管理,尤其是加了 Propagation.REQUIRES_NEW 之后我们的事务就会在方法执行之后提交事务,或是加了 synchronized 关键字之后,这就是一个原子操作了,这是不对的哈。为什么呢?还要从代理类说起,代理类开启事务,执行目标方法,提交事务。而不管是 REQUIRES_NEW 还是 synchronized 关键字都只是作用于目标方法,即使目标方法执行成功,可是事务还是没有提交呢。

对于 insert,delete,update,select --- for update,语句来说,都是原子性的。但是 select 不是。

解决方案就是扩大 synchronized 的范围,为整个代理方法加锁,而不是把锁加在目标方法上。也可以通过 SQL 来控制,保证操作的原子性,使用 select --- for update

原作者:非正经程序员
链接:https://juejin.cn/post/7023778837456486436

聊聊@Transactional 的失效场景,有哪些坑?的更多相关文章

  1. @Transactional 注解失效场景

    @Transactional可以用在接口.类.类方法上. 作用于类:当把@Transactional注解放在类上时,表示该类的所有public方法都配置了该事物注解. 作用于方法:表示该方法配置了事物 ...

  2. 一口气说出 6种,@Transactional注解的失效场景

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...

  3. @Transactional注解的失效场景

    一口气说出 6种,@Transactional注解的失效场景 计算机java编程 发布时间: 20-03-1912:35优质科技领域创作者 引言 昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Tr ...

  4. Spring中@Translational注解失效场景

    今天面试被问到@Translational注解什么场景下失效,我一脸懵逼,说的恍恍惚惚的,下来我就总结一下@Translational注解失效的场景! @Transactional 注解相信大家并不陌 ...

  5. 聊聊spring事务失效的12种场景,太坑了

    前言 对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了. 在某些业务场景下,如果一个请求中,需要同时写入多张表的数据.为了保证操作的原子性(要么同时成功,要么同时失败),避免数据 ...

  6. 面试突击83:什么情况会导致@Transactional事务失效?

    一个程序中不可能没有事务,而 Spring 中,事务的实现方式分为两种:编程式事务和声明式事务,又因为编程式事务实现相对麻烦,而声明式事务实现极其简单,所以在日常项目中,我们都会使用声明式事务 @Tr ...

  7. 《MySQL面试小抄》索引失效场景验证

    我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点!!! 本期主要面试考点 面试官考点之什么情况下会索 ...

  8. 就这?Spring 事务失效场景及解决方案

    小明:靓仔,我最近遇到了很邪门的事. 靓仔:哦?说来听听. 小明:上次看了你的文章<就这?一篇文章让你读懂 Spring 事务>,对事务有了详细的了解,但是在项目中还是遇到了问题,明明加了 ...

  9. spring @Transactional事务失效

    不开事务几种情形 ① @Transactional写在了private方法上 org.springframework.transaction.interceptor.AbstractFallbackT ...

随机推荐

  1. 支付宝openssl_sign(): supplied key param cannot be coerced into a private key in

    先说一下,生成rsa 私钥 公钥的方法,以ubuntu 为例sudo apt-get install openssl # 先装上这个库genrsa -out rsa_private_key.pem 1 ...

  2. Jmeter系列(17)- 常用断言之JSON断言

    模块分析 Assert JSON Path exists:需要断言的 JSON 表达式 Additionally assert value:如果要根据值去断言,请勾选 Match as regular ...

  3. Shell系列(28)- 条件判断之字符串判断

    字符串判断 $变量时要用双引号引起来,即"$变量" 测试选项 作用 -z 字符串 判断字符串是否为空(为空返回真) -n 字符串 判断字符串是否为非空(非空返回真) 字符串1 == ...

  4. sqlalchemy ————关联表

    1.创建模型的时候做外键关联 class UI_ID(db.Model): __tablename__ = 'ui_id' id = db.Column(INTEGER(11), primary_ke ...

  5. 关于python中的可哈希与不可哈希

    可哈希:简要的说可哈希的数据类型,即不可变的数据结构(字符串str.元组tuple.对象集objects).它是一个将大体量数据转化为很小数据的过程,甚至可以仅仅是一个数字,以便我们可以用在固定的时间 ...

  6. MFC读写.txt文件时进度条显示实时进度

    整体实现方式:先获得文件长度,然后用每次读取的长度,计算出完成的百分比,用百分比的值设置进度条. 一.MFC进度条 Progress Control 相关函数 1. create() --创建Prog ...

  7. if 条件

    返回不能被2整除的数 result = [] iter = range(10) for i in iter: if i % 2: print(f'{i}', i % 2) result.append( ...

  8. Python可视化--HA Candle作图

    Overview 最近遇到一个问题,做蜡烛图,怎么把x轴变为时间轴,candlestick_ohlc中的date数据需要时整数型,当选择其他类型数据时,会报错! 具体文章请参考:如何用Python计算 ...

  9. python-docx处理Word必备工具

      我的理解 为什么会用到python-docx,因为近段时间下载了大量网文,但格式都是html的,我个人习惯使用word处理文字,于是就想法设法把html文档转换为word,首先要考虑的问题就是从h ...

  10. 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析

    SudokuSolver 2.1 程序实现 在 2.0 版的基础上,2.1 版在输出信息上做了一些改进,并增加了 runtil <steps> 命令,方便做实例分析. CQuizDeale ...