解决@Transaction注解导致动态切换更改数据库失效问题

 更新时间:2021年09月07日 09:57:34   作者:仑小杰  
这篇文章主要介绍了解决@Transaction注解导致动态切换更改数据库失效问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

@Transaction注解导致动态切换更改数据库失效

使用场景

  • 给所有的Controller方法上加切点
  • 在@Before注解的方法里,根据http请求中携带的header,动态切换数据源
  • 使用mybatis或者jpa执行操作

遇到问题

当给Controller方法加上@Transaction注解后,动态切换数据源就失效了,原因是每次@Before注解的方法运行之前,protected abstract Object determineCurrentLookupKey();就已经运行了,而这个方法是切换数据源的关键。

解决

其实也算不上解决,就是不要在Controller方法上加事务注解,非要加事务,中间的Service层就不要省了。

@Transactional失效的场景及原理

1.@Transactional修饰的方法

为非public方法,这个时候@Transactional会实现。

失败的原理是:@Transactional是基于动态代理来实现的,非public的方法,他@Transactional的动态代理对象信息为空,所以不能回滚。

2.在类内部没有添加@Transactional的方法

调用了@Transactional方法时,当你调用是,他也不会回滚

测试代码如下

@Service
public class UserServiceImpl extends BaseServiceImpl<UserEntity> implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    @Transactional
    public void insertOne() {
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername("Michael_C_2019");
        //插入到数据库
        userMapper.insertSelective(userEntity);
        //手动抛出异常
        throw new IndexOutOfBoundsException();
    }
    @Override
    public void saveOne() {
        insertOne();
    }
}

失败的原理:@Transactional是基于动态代理对象来实现的,而在类内部的方法的调用是通过this关键字来实现的,没有经过动态代理对象,所以事务回滚失效。

3.就是在@Transactional方法内部捕获了异常

没有在catch代码块里面重新抛出异常,事务也不会回滚。

代码如下:

@Override
    @Transactional
    public void insertOne() {
        try {
            UserEntity userEntity = new UserEntity();
            userEntity.setUsername("Michael_C_2019");
            //插入到数据库
            userMapper.insertSelective(userEntity);
            //手动抛出异常
            throw new IndexOutOfBoundsException();
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }

所以在阿里巴巴的Java开发者手册里面有明确规定,在 @Transactional的方法里面捕获了异常,必须要手动回滚,

代码如下:

 @Override
    @Transactional
    public void insertOne() {
        try {
            UserEntity userEntity = new UserEntity();
            userEntity.setUsername("Michael_C_2019");
            //插入到数据库
            userMapper.insertSelective(userEntity);
            //手动抛出异常
            throw new IndexOutOfBoundsException();
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

失败原理:这时候我们来看看spring的源码:

TransactionAspectSupport类里面的invokeWithinTransaction方法

TransactionAspectSupport
@Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
        TransactionAttributeSource tas = this.getTransactionAttributeSource();
        TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
        PlatformTransactionManager tm = this.determineTransactionManager(txAttr);
        String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
        Object result;
        if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {
            TransactionAspectSupport.ThrowableHolder throwableHolder = new TransactionAspectSupport.ThrowableHolder(null);
            try {
                result = ((CallbackPreferringPlatformTransactionManager)tm).execute(txAttr, (status) -> {
                    TransactionAspectSupport.TransactionInfo txInfo = this.prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                    Object var9;
                    try {
                        Object var8 = invocation.proceedWithInvocation();
                        return var8;
                    } catch (Throwable var13) {
                        if (txAttr.rollbackOn(var13)) {
                            if (var13 instanceof RuntimeException) {
                                throw (RuntimeException)var13;
                            }
                            throw new TransactionAspectSupport.ThrowableHolderException(var13);
                        }
                        throwableHolder.throwable = var13;
                        var9 = null;
                    } finally {
                        this.cleanupTransactionInfo(txInfo);
                    }
                    return var9;
                });
                if (throwableHolder.throwable != null) {
                    throw throwableHolder.throwable;
                } else {
                    return result;
                }
            } catch (TransactionAspectSupport.ThrowableHolderException var19) {
                throw var19.getCause();
            } catch (TransactionSystemException var20) {
                if (throwableHolder.throwable != null) {
                    this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                    var20.initApplicationException(throwableHolder.throwable);
                }
                throw var20;
            } catch (Throwable var21) {
                if (throwableHolder.throwable != null) {
                    this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                }
                throw var21;
            }
        } else {
            TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            result = null;
            try {
                result = invocation.proceedWithInvocation();
            } catch (Throwable var17) {
              //异常时,在catch逻辑中回滚事务
                this.completeTransactionAfterThrowing(txInfo, var17);
                throw var17;
            } finally {
                this.cleanupTransactionInfo(txInfo);
            }
            this.commitTransactionAfterReturning(txInfo);
            return result;
        }
    }

他是通过捕获异常然后在catch里面进行事务的回滚的,所以如果你在自己的方法里面catch了异常,catch里面没有抛出新的异常,那么事务将不会回滚。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 解决maven没有打包xml文件的问题

    解决maven没有打包xml文件的问题

    这篇文章主要介绍了解决maven没有打包xml文件的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • java两个线程同时写一个文件

    java两个线程同时写一个文件

    这篇文章主要为大家详细介绍了java两个线程同时写一个文件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • SpringBoot实现跨域的几种常用方式总结

    SpringBoot实现跨域的几种常用方式总结

    跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,或者涉及到两个不同域名的资源之间的交互,由于同源策略(Same Origin Policy)的限制,浏览器不允许跨域请求,本文小编给大家分享了SpringBoot实现跨域的几种常用方式,需要的朋友可以参考下
    2023-09-09
  • SpringCloudConfig之client端报错Could not resolve placeholder问题

    SpringCloudConfig之client端报错Could not resolve placeholder问

    这篇文章主要介绍了SpringCloudConfig之client端报错Could not resolve placeholder ‘from‘ in value “${from}“问题及解决方案,具有很好的参考价值,希望对大家有所帮助
    2022-12-12
  • SpringBoot如何实现定时任务示例详解

    SpringBoot如何实现定时任务示例详解

    使用定时任务完成一些业务逻辑,比如天气接口的数据获取,定时发送短信,邮件。以及商城中每天用户的限额,定时自动收货等等,这篇文章主要给大家介绍了关于SpringBoot如何实现定时任务的相关资料,需要的朋友可以参考下
    2021-10-10
  • Spring单元测试控制Bean注入的方式

    Spring单元测试控制Bean注入的方式

    这篇文章主要介绍了Spring单元测试控制Bean注入的方式,其中续注意的是在Bean上加@Order(xxx)是无法控制bean注入的顺序的,需要的可以参考一下
    2023-04-04
  • JAVA实现读取txt文件内容的方法

    JAVA实现读取txt文件内容的方法

    本篇文章主要介绍了JAVA实现读取txt文件内容的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • Idea配置maven-tomcat-plugin插件实现项目部署

    Idea配置maven-tomcat-plugin插件实现项目部署

    今天小编就为大家分享一篇关于Idea配置maven-tomcat-plugin插件实现项目部署,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02
  • SpringBoot利用注解来实现Redis分布式锁

    SpringBoot利用注解来实现Redis分布式锁

    有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。本文将利用注解来实现Redis分布式锁,需要的可以参考一下
    2022-09-09
  • java HttpClient传输json格式的参数实例讲解

    java HttpClient传输json格式的参数实例讲解

    这篇文章主要介绍了java HttpClient传输json格式的参数实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01

最新评论