当前位置: 代码网 > it编程>编程语言>Java > 解决Spring事务@Transactional多层嵌套失效问题

解决Spring事务@Transactional多层嵌套失效问题

2024年11月09日 Java 我要评论
场景在 aservice 中,我会直接调用 a 的数据操作层去操作 a的数据 以及 a关联密切的其它数据,在操作完之后,会去调用 bservice 和 cservice 中更新对应的数据,并在每个方法

场景

在 aservice 中,我会直接调用 a 的数据操作层去操作 a的数据 以及 a关联密切的其它数据,在操作完之后,会去调用 bservice 和 cservice 中更新对应的数据,并在每个方法上使用了事务,但在调用 bservice 或者 cservice 时候出现了异常,此时出现异常的bservice 或者 cservice 中数据没有改变,回滚了。

但在 aservice 中调用的 update 方法和出现异常前已经执行完的方法执行成功并且没有回滚。

伪代码如下:

1、aservice实现类

@service
@slf4j
public class aserviceimpl implements iaservice {
    private final ibservice bservice;
    private final icservice cservice;
    public aserviceimpl(ibservice bservice, icservice cservice) {
        this.bservice = bservice;
        this.cservice = cservice;
    }
    
    @override
    @transactional(rollbackfor = exception.class)
    public string modifya(testaparam param) throws baseexception {
        // 兜底:入参空字段校验
        this.judgenull(param);

        testmodifyparam modifyparam = param.getmodifyparam();

        string acode = update(param, boolean.true);

        if (null != modifyparam.getstatus() && teststatusconstant.edited.equals(modifyparam.getstatus())){
            // 保存b信息
            bservice.saveinfo(param, acode);
            // 保存c信息
            cservice.saveinfo(param, acode);
        }

        // 更新a数据关联其它数据
        if (stringutils.isnotblank(acode)){
            setotherdata(param);
        }

        return acode;
    }

    @transactional(rollbackfor = exception.class)
    public void setotherdata(testaparam param) throws baseexception {
        // 其它关联数据处理
    }

    @transactional(rollbackfor = exception.class)
    public string update(testaparam param, boolean directlyflag) throws baseexception {
        // 更新处理
       return null;
    }

    @transactional(rollbackfor = exception.class)
    public void judgenull(testaparam param) throws baseexception {
        // 参数空校验与提醒处理
    }

}

2、bservice 和 cservice 实现类(cservice实现类异常处理差不多相同)

@service
@slf4j
public class bserviceimpl implements ibservice {
    @override
    @transactional(rollbackfor = exception.class)
    public void saveinfo(testaparam param, string acode) throws baseexception {
        // 数据校验

        // 模拟代码,出现不匹配数据错误情况会抛出异常
        if (objects.isnull(param)){
            throw new baseexception(codeenum.failed.getcode(),"bserviceimpl saveinfo param mistake");
        }

        // 其它操作

    }
}

一、spring事务实现方式及原理

spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的。

一般我们在程序里面使用的都是在方法上面加 @transaction 注解,这种属于声明式事物

声明式事务本质是通过 aop 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

二、事务失效原因

2.1 数据库本身不支持事物

这里以 mysql 为例,其 myisam 引擎是不支持事务操作的,innodb 才是支持事务的引擎,一般要支持事务都会使用 innodb。

2.2 方法不是public

注解 @transactional 只能放在 public 修饰的方法上才起作用private 方法是不会被spring代理的)因此是不会有事物产生的,这种做法是无效的。

2.3 未被 spring 管理的bean

没有被spring管理的bean, spring连代理对象都无法生成,事务自然是无效的。

2.4 当前类的调用

@service
public class userserviceimpl implements userservice {

    public void update(user user) {
        updateuser(user);
    }

    @transactional(rollbackfor = exception.class)
    public void updateuser(user user) {
        // update user
    }

}

上面的这种情况下是不会有事物管理操作的。

通过看声明式事物的原理可知,spring使用的是aop切面的方式本质上使用的是动态代理来达到事物管理的目的,当前类调用的方法上面加 @transactional 这个是没有任何作用的,因为调用这个方法的是this。

再看下面的一种例子:

@service
public class userserviceimpl implements userservice {

    @transactional(rollbackfor = exception.class)
    public void update(user user) {
        updateuser(user);
    }

    @transactional(propagation = propagation.requires_new)
    public void updateuser(user user) {
        // update user
    }

}

这次在 update 方法上加了 @transactional,updateuser 加了 requires_new 新开启一个事务,那么新开的事务管用么?

答案是:不管用!

因为它们发生了自身调用,就调该类自己的方法,而没有经过 spring 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了。

还有就是在没有指定事务传播行为时,从源码中可以看到默认是使用 propagation.required。

2.5 配置的事物传播性有问题

@service
public class userserviceimpl implements userservice {

    @transactional(propagation = propagation.not_supported)
    public void update(user user) {
        // update user
    }    
}

2.6 异常被你 "抓住"了

@service
public class userserviceimpl implements userservice {

    @transactional(rollbackfor = exception.class)
    public void update(user user) {

      try{
        // update user
      }catch(execption e){
         log.error("异常",e)
      }
    }    
}

异常被抓了,这样子代理类就没办法知道你到底有没有错误,需不需要回滚,所以这种情况也是没办法回滚。

2.7 rollbackfor 异常指定错误

@service
public class userserviceimpl implements userservice {

    @transactional
    public void update(user user) {
        // update user
    }    
}

上面这种没有指定回滚异常,这个时候默认的回滚异常是 runtimeexception ,如果出现其他异常那么就不会回滚事物。

三、spring的事务传播行为

spring 事务的传播行为说的是,当多个事务同时存在的时候, spring 如何处理这些事务的行为。

类型说明
propagation_required如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
propagation_supports支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
propagation_mandatory支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
propagation_requires_new创建新事务,无论当前存不存在事务,都创建新事务。
propagation_not_supported以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
propagation_never以非事务方式执行,如果当前存在事务,则抛出异常。
propagation_nested如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 required 属性执行。

当传播行为设置了propagation_not_supported,propagation_never,propagation_supports这三种时,就有可能存在事物不生效。

四、解决思路

1、声明式事务处理,采用合适的事务传播行为,将aservice中修改a中数据方法update和更新a数据关联其它数据方法setotherdata通过appcontext.getbean()的方式直接获取aservice中的方法,不违背事务失效原因中的2.4项(当前类的调用),并将保持b和c信息的方法单独抽取出来,提供一个savebandcinfo方法,加上事务处理和传播行为,并抛出异常信息。

2、使用编程式事务管理,手动配置事务边界,确保modifya中所有方法在事务中执行。

五、采取方法

由于aservice中的方法 modifya 调用链路比较长(业务急需处理好),如果使用声明式事务处理,改动起来是比较大的,中间链路可能会存在事务传播行为失效的情况,此时使用编程式事务管理解决就会很明显轻松解决。(这方法并不建议大家日常使用,建议使用声明事务更好点)

伪代码如下:

@service
@slf4j
public class aserviceimpl implements iaservice {
    private final ibservice bservice;
    private final icservice cservice;
    private final platformtransactionmanager transactionmanager;
    public aserviceimpl(ibservice bservice, icservice cservice, platformtransactionmanager transactionmanager) {
        this.bservice = bservice;
        this.cservice = cservice;
        this.transactionmanager = transactionmanager;
    }
    
    @override
    @transactional(rollbackfor = exception.class)
    public string modifya(testaparam param) throws baseexception {
        // 兜底:入参空字段校验
        this.judgenull(param);
        // 编程式事务
        defaulttransactiondefinition def = new defaulttransactiondefinition();
        transactionstatus status = transactionmanager.gettransaction(def);
        try {
            testmodifyparam modifyparam = param.getmodifyparam();
            string acode = update(param, boolean.true);
            if (null != modifyparam.getstatus() && teststatusconstant.edited.equals(modifyparam.getstatus())){
                // 保存b信息
                bservice.saveinfo(param, acode);
                // 保存c信息
                cservice.saveinfo(param, acode);
            }
            // 更新a数据关联其它数据
            if (stringutils.isnotblank(acode)){
                setotherdata(param);
            }
            // 提交事务
            transactionmanager.commit(status);
            return acode;
        } catch (exception e) {
            // 回滚事务
            transactionmanager.rollback(status);
            // 处理异常或根据需要重新抛出异常
            throw new baseexception(codeenum.failed.getcode(),"modifya error message is {}", e.getmessage());
        }
            
    }

    @transactional(rollbackfor = exception.class)
    public void setotherdata(testaparam productstrategydo) throws baseexception {
        // 其它关联数据处理
    }

    @transactional(rollbackfor = exception.class)
    public string update(testaparam param, boolean directlyflag) throws baseexception {
        // 更新处理
        return null;
    }

    @transactional(rollbackfor = exception.class)
    public void judgenull(testaparam param) throws baseexception {
        // 参数空校验与提醒处理
    }

上述代码使用了 platformtransactionmanager接口的实现来手动管理事务。

在代码中,我们首先获取 transactionmanager的实例,然后使用该实例手动创建事务定义和事务状态。在try块中执行update方法和其它方法,并在最后根据执行情况手动提交或回滚事务。

通过这种方式,可以确保update方法的操作在事务中进行,且在其它方法中发生异常时能够回滚。如果采取这个方式请确保在相应的配置类中将transactionmanager正确配置为适用于你的应用程序的事务管理器实现。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com