Spring是如何管理事务的之@Transactional注解详解

 
 

往期热门文章:

1、去腾讯面试,直接让人出门左拐 :幂等性都不知道!
2、面试官:如果要存 IP 地址,用什么数据类型比较好?99%人都会答错!
3、面试官:为什么不能将实数作为 HashMap 的 key?
4、Redis 官方可视化工具,高颜值,功能太强大!
5、不好意思, Maven 该换了!

背景

前两天在工作中忙的焦头烂额,涉及到 @Transactional 对于事务的控制,便仔细研究了一下,颇有所获,花费好了几天测试整理,今天才发表出来,希望老铁们能有所获吧。

Spring 事务的传播行为

话不多说直奔正题。先简单介绍一下 Spring 事务的传播行为。

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

Spring事务的回滚机制

然后说一下Spring事务的回滚机制:Spring 的 AOP 即声明式事务管理默认是针对 unchecked exception 回滚。

Spring 的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行 commit or rollback(Spring 默认取决于是否抛出 runtimeException)。

如果你在方法中有 try{}catch(Exception e){} 处理,那么 try 里面的代码块就脱离了事务的管理,若要事务生效需要在 catch 中 throw new RuntimeException ("xxxxxx"); 这一点也是面试中会问到的事务失效的场景。

@Transactional 注解底层实现

再简单介绍一下 @Transactional 注解底层实现方式吧,毫无疑问,是通过动态代理,那么动态代理又分为 JDK 自身和 CGLIB,这个也不多赘述了,毕竟今天的主题是如何将 @Transactional 对于事物的控制应用到炉火纯青。哈哈~

第一点要注意的就是在 @Transactional 注解的方法中,再调用本类中的其他方法 method2 时,那么 method2 方法上的 @Transactional 注解是不!会!生!效!的!

但是加上也并不会报错,拿图片简单帮助理解一下吧。这一点也是面试中会问到的事务失效的场景。

aa20a004d7aa281bc9c9df1f3c81d2ff.png

通过代理对象在目标对象前后进行方法增强,也就是事务的开启提交和回滚。那么继续调用本类中其他方法是怎样呢?

如下图:

b90aabf2488d394c68a8932efaf0214f.png

可见目标对象内部的自我调用,也就是通过 this. 指向的目标对象将不会执行方法的增强。

先说第二点需要注意的地方,等下说如何解决上面第一点的问题。第二点就是 @Transactional 注解的方法必须是公共方法,就是必须是 public 修饰符!!!

至于这个的原因,发表下个人的理解吧,因为 JVM 的动态代理是基于接口实现的,通过代理类将目标方法进行增强。

想一下也是啦,没有权限访问那么你让我怎么进行?好吧,这个我也没有深入研究底层,个人理解个人理解。

在这里我也放个问题吧,希望有高手可以回复指点指点我,因为 JVM 动态代理是基于接口实现的,那么是不是 service 层都要按照接口和实现类的开发模式,注解才会生效呢。

就是说 controller 层直接调用没有接口的 service 层,加了注解也一样不起作用吧,这个懒了,没有测试,其一是因为没有人会这么开发吧,其二是我就认为是不起作用的,哈哈。

下面来解决一下第一点的问题,如何在方法中调用本类中其他方法呢。

通过 AopContext.currentProxy () 获取到本类的代理对象,再去调用就好啦。

因为这个是 CGLIB 实现,所以要开启 AOP,当然也很简单,在 springboot 启动类上加上注解 @EnableAspectJAutoProxy(exposeProxy = true) 就可以啦,这个依赖大家自行搜一下就好啦。

要注意,注意,代理对象调用的方法也要是 public 修饰符,否则方法中获取不到注入的 bean,会报空指针错误。

emmmm,我先把调用的方式和结果说下吧。自己简单写了代码,有点粗糙,就不要介意啦,嘿嘿!

Controller 中调用 Service:

@RestController
public class TransactionalController {

    @Autowired
    private TransactionalService transactionalService;

    @PostMapping("transactionalTest")
    public void transacionalTest(){
        transactionalService.transactionalMethod();
    }
}

Service 中实现对事务的控制:接口

public interface TransactionalService {
    void transactionalMethod();
}

Service 中实现对事务的控制:实现类(各种情况的说明都写在图片里了,这样方便阅读,有助于快速理解吧)

2b8af0362a086dd381a7d405ac4ac4fc.png

7eee1f1020fffa0b36002ba45cde4116.png

上面两种情况不管使不使用代理调用方法 1 和方法 2,方法 transactionalMethod 都处在一个事务中,四条更新操作全部失败。

那么有人可能会有疑问了,在方法 1 和方法 2 上都加 @Transactional 注解呢?答案是结果和上面是一致的。

小结:只要方法 transactionalMethod 上有注解,并且方法 1 和方法 2 都处于当前事务中(不使用代理调用,方法 1 和方法 2 上的 @Transactional 注解是不生效的;使用代理,需要方法 1 和方法 2 都处在 transactionalMethod 方法的事务中,默认或者嵌套事务均可,当然也可以不加 @Transactional 注解),那么整体保持事务一致性。

如果想要方法 1 和方法 2 均单独保持事务一致性怎么办呢,刚说过了,如果不是用代理调用 @Transactional 注解是不生效的,所以一定要使用代理调用实现,然后让方法 1 和方法 2 分别单独开启新的事务,便 OK 啦。

下面摆上图片:

befc2fa292415da02f2ed59f3ae4302e.png

429e77c2679ad8efeef0531f1b81458c.png

这两种情况都是方法 1 和方法 2 均处在单独的事务中,各自保持事务的一致性。

接下来进行进一步的优化,可以在 transactionalMethod 方法中分别对方法 1 和方法 2 进行控制。

要将代码的艺术发挥到极致嘛,下面装逼开始。

e2a831d39ea51c6183aaa9d4737669cd.png

代码太长了,超过屏幕了,粘贴出来截的图,红框注释需要仔细看,希望不要影响你的阅读体验,至此,本篇关于 @Transactioinal 注解的使用就到此为止啦,

总结

简单总结一下吧:

  • 就是 @Transactional 注解保证的是每个方法处在一个事务,如果有 try 一定在 catch 中抛出运行时异常。

  • 方法必须是 public 修饰符。否则注解不会生效,但是加了注解也没啥毛病,不会报错,只是没卵用而已。

  • this. 本方法的调用,被调用方法上注解是不生效的,因为无法再次进行切面增强。

转自:范学博

链接:https://blog.csdn.net/fanxb92/article/details/81296005

 
 
 
 
最近热文阅读:

1、面试官:如果要存 IP 地址,用什么数据类型比较好?99%人都会答错!

2、面试官:为什么不能将实数作为 HashMap 的 key?

3、Redis 官方可视化工具,高颜值,功能太强大!

4、不好意思, Maven 该换了!

5、面试官 | Spring Boot 项目如何统一结果,统一异常,统一日志?

6、基于SpringBoot+MyBatis+Vue的音乐网站

7、聊聊接口优化的几种方法

8、面试官 | Spring Boot 项目如何统一结果,统一异常,统一日志?

9、为什么不建议使用ON DUPLICATE KEY UPDATE?

10、Java8 Stream,过分丝滑!

42b4037e56c2c6f77505dfc1226ff06a.jpeg

关注公众号,你想要的Java都在这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值