前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >@Transactional 自调用失效问题解析

@Transactional 自调用失效问题解析

作者头像
明明如月学长
发布2021-08-31 14:39:26
1.1K0
发布2021-08-31 14:39:26
举报
文章被收录于专栏:明明如月的技术专栏

一、背景

”脏脏包“在技术群里问了一个问题:”大家有在项目中遇到这样的场景吗 在一个service层重写的方法中调用一个私有方法。 service重写的方法不加事务 私有方法想加入事务 他去调用私有方法时 私有方法需要被事务控制“ 。

这个问题比较典型,面试时也经常被问到,在此简单整理一下。

二、Spring注解方式的事务实现机制

在应用调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器(图 2 有相关介绍)AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务, 如图 1 所示。

图 1. Spring 事务实现机制

Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,图 1 是以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。

对于 JdkDynamicAopProxy,需要调用其 invoke 方法。

三、分析

3.1 为什么@Transactional 只能应用到 public 方法才有效?

3.1.1 从理论角度

理论上@Transactional 注解是为了进行事务增强。

JDK 动态代理,比如需事先接口才行,因此必然是 public的。

AspectJ 动态代理,是基于类的代理,如果该类的方法为私有,那么子类中就无法重写和调用。

3.1.2 从源码角度

这是因为在使用 Spring AOP 代理时,Spring 在调用在的 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取 @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

其源码如下:

代码语言:javascript
复制
private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) {
// 关键在这里
            if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
                return null;
            } else {
                Class userClass = ProxyUtils.getUserClass(targetClass);
                Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
                specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                TransactionAttribute txAtt = null;
                if (specificMethod != method) {
                    txAtt = this.findTransactionAttribute(method);
                    if (txAtt != null) {
                        return txAtt;
                    }

                    txAtt = this.findTransactionAttribute(method.getDeclaringClass());
                    if (txAtt != null || !this.enableDefaultTransactions) {
                        return txAtt;
                    }
                }

                txAtt = this.findTransactionAttribute(specificMethod);
                if (txAtt != null) {
                    return txAtt;
                } else {
                    txAtt = this.findTransactionAttribute(specificMethod.getDeclaringClass());
                    if (txAtt != null) {
                        return txAtt;
                    } else if (!this.enableDefaultTransactions) {
                        return null;
                    } else {
                        Method targetClassMethod = this.repositoryInformation.getTargetClassMethod(method);
                        if (targetClassMethod.equals(method)) {
                            return null;
                        } else {
                            txAtt = this.findTransactionAttribute(targetClassMethod);
                            if (txAtt != null) {
                                return txAtt;
                            } else {
                                txAtt = this.findTransactionAttribute(targetClassMethod.getDeclaringClass());
                                return txAtt != null ? txAtt : null;
                            }
                        }
                    }
                }
            }
        }

非公有函数事务属性信息返回null

3.2 为什么自调用无效?

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。

若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

代码语言:javascript
复制
@Service
public class OrderService {

    private void insert() {
insertOrder();
}

@Transactional
    public void insertOrder() {
        //SQL操作
       }
}

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

四、解决方法

4.1 可以使用ApplicatonContextHolder 工具类,从上下文中获取当前bean,再调用。

4.2 可以使用上下文工具类获取当前对象的代理类  @EnableAspectJAutoProxy (exposeProxy = true) 然后通过下面方法获取代理对象,然后再调用

代码语言:javascript
复制
@Service
public class OrderService {

  public void insert() {

    OrderService proxy = (OrderService) AopContext.currentProxy();
       proxy.insertOrder();
    }

    @Transactional
    public void insertOrder() {
        //SQL操作
       }
}

4.3  maven中加入spring-aspects 和 aspectjrt 的依赖以及 aspectj-maven-plugin插件

注:

第二节和第三节部分内容转载自:

透彻的掌握 Spring 中@transactional 的使用

创作不易,如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/06/26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、Spring注解方式的事务实现机制
  • 三、分析
    • 3.1 为什么@Transactional 只能应用到 public 方法才有效?
      • 3.1.1 从理论角度
        • 3.1.2 从源码角度
          • 3.2 为什么自调用无效?
          • 四、解决方法
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档