前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring声明式事务处理的实现原理,来自面试官的穷追拷问

Spring声明式事务处理的实现原理,来自面试官的穷追拷问

作者头像
JavaQ
发布2020-04-24 14:00:29
2.7K0
发布2020-04-24 14:00:29
举报
文章被收录于专栏:JavaQJavaQJavaQ

面试官:有如下代码场景,A类的a1方法没有标注@Transactional注解,a2方法标注了@Transactional注解,那么在a1方法里调用a2方法,此时会开始事务吗?

小小白:不会开启事务。

面试官:解释一下为什么?

小小白:a1方法是目标类A的原生方法,调用a1的时候即直接进入目标类A进行调用,在目标类A里面只有a2的原生方法,在a1里调用a2,即直接执行a2的原生方法,并不通过创建代理对象进行调用,所以并不会进入TransactionInterceptor的invoke方法,不会开启事务。

面试官:那此时如果在a1方法上标注@Transactional注解,a2方法不标注@Transactional注解,但是a1方法的访问修饰符是protected,在a1方法里调用a2方法会开始事务吗?

小小白:也不会开启事务。

面试官:根据你上面的回答改了代码,为什么还是不会开启事务?

小小白:@Transactional的工作机制是基于AOP实现的,而AOP是使用动态代理实现的,动态代理要么是JDK方式、要么是Cglib方式。如果是JDK动态代理的方式,根据上面的分析可以知道,目标类的目标方法是在接口中定义的,也就是必须是public修饰的方法才可以被代理。如果是Cglib方式,代理类是目标类的子类,理论上可以代理public和protected方法,但是Spring在进行事务增强是否能够应用到当前目标类判断的时候,遍历的是目标类的public方法,所以Cglib方式也只对public方法有效。

面试官:Spring框架中声明式事务处理是如何实现的?

小小白:Spring容器在初始化每个单例bean的时候,会遍历容器中的所有BeanPostProcessor实现类,并执行其postProcessAfterInitialization方法,在执行AbstractAutoProxyCreator类的postProcessAfterInitialization方法时会遍历容器中所有的切面,查找与当前实例化bean匹配的切面,这里会获取事务属性切面,查找@Transactional注解及其属性值,然后根据得到的切面创建一个代理对象,默认是使用JDK动态代理创建代理,如果目标类是接口,则使用JDK动态代理,否则使用Cglib。在创建代理的过程中会获取当前目标方法对应的拦截器,此时会得到TransactionInterceptor实例,在它的invoke方法中实现事务的开启和回滚,在需要进行事务操作的时候,Spring会在调用目标类的目标方法之前进行开启事务、调用异常回滚事务、调用完成会提交事务。是否需要开启新事务,是根据@Transactional注解上配置的参数值来判断的。如果需要开启新事务,获取Connection连接,然后将连接的自动提交事务改为false,改为手动提交。当对目标类的目标方法进行调用的时候,若发生异常将会进入completeTransactionAfterThrowing方法。

面试官:能否通俗的讲述一下它的实现原理?

小小白:如果在类A上标注Transactional注解,Spring容器会在启动的时候,为类A创建一个代理类B,类A的所有public方法都会在代理类B中有一个对应的代理方法,调用类A的某个public方法会进入对应的代理方法中进行处理;如果只在类A的b方法(使用public修饰)上标注Transactional注解,Spring容器会在启动的时候,为类A创建一个代理类B,但只会为类A的b方法创建一个代理方法,调用类A的b方法会进入对应的代理方法中进行处理,调用类A的其它public方法,则还是进入类A的方法中处理。在进入代理类的某个方法之前,会先执行TransactionInterceptor类中的invoke方法,完成整个事务处理的逻辑,如是否开启新事务、在目标方法执行期间监测是否需要回滚事务、目标方法执行完成后提交事务等。

面试官:Spring框架对事务回滚的实现,是不是对所有类型的异常都会进行事务回滚操作?

小小白:Spring并不会对所有类型异常都进行事务回滚操作,默认是只对Unchecked Exception(Error和RuntimeException)进行事务回滚操作。

面试官:有没有遇到过Transactional注解的不合理用法?

小小白:当下对数据库连接的使用基本上都用连接池技术,每个应用会根据环境和自身需求设置一些合适的连接池配置,如果每个连接都一直被长时间占用,会导致数据库连接数不够用、系统各项压力指标不断攀升、系统缓慢等问题,所以说连接池中的每一个连接都是很昂贵的。那么问题就来了,只要需要事务就需要占用一个数据库连接,如果在需要开启事务的方法里进行一些IO操作、网络通讯等需要长时间处理的操作,这个数据库连接就一直被占用着,直到方法执行结束后自动提交事务或执行过程中发生异常回滚事务,这个数据库连接才会被释放掉。这个过程中还有一个很可怕的问题,如果在需要开启事务的方法里进行了网络通讯操作,而这个操作没有设置网络超时时间,那这个数据库连接就会被一直占用着。上述问题,在流量很大的情况下简直就是灾难,会直接导致应用系统挂掉。

面试官:如何解决这些问题?

小小白:正确的使用Transactional注解需要做到如下四点:

  • 不要在类上标注Transactional注解,要在需要的方法上标注。即使类的每个方法都需要事务也不要在类上标注,因为有可能你或别人新添加的方法根本不需要事务;
  • 标注了Transactional注解的方法体中不要涉及耗时很久的操作,如IO操作、网络通信等;
  • 根据业务需要设置合适的事务参数,如是否需要新事务、超时时间等;
  • 控制事务影响的范围,代码中减少事务影响的代码。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JavaQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档