解决@Transaction注解导致动态切换更改数据库失效问题
目录
- @Transaction注解导致动态切换更改数据库失效
- 使用场景
- 遇到问题
- 解决
- @Transactional失效的场景及原理
- 1.@Transactional修饰的方法
- 2.在类内部没有添加@Transactional的方法
- 3.就是在@Transactional方法内部捕获了异常
@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方法时,当你调用是,他也不会回滚
测试代码如下
@Servicepublic class UserServiceImpl extends BaseServiceImplimplements UserService {@Autowiredprivate UserMapper userMapper; @Override@Transactionalpublic void insertOne() {UserEntity userEntity = new UserEntity(); userEntity.setUsername("Michael_C_2019"); //插入到数据库userMapper.insertSelective(userEntity); //手动抛出异常throw new IndexOutOfBoundsException(); }@Overridepublic void saveOne() {insertOne(); }}
失败的原理:@Transactional是基于动态代理对象来实现的,而在类内部的方法的调用是通过this关键字来实现的,没有经过动态代理对象,所以事务回滚失效。
3.就是在@Transactional方法内部捕获了异常
没有在catch代码块里面重新抛出异常,事务也不会回滚。
代码如下:
@Override@Transactionalpublic 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的方法里面捕获了异常,必须要手动回滚,
【解决@Transaction注解导致动态切换更改数据库失效问题】代码如下:
@Override@Transactionalpublic 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
@Nullableprotected 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里面没有抛出新的异常,那么事务将不会回滚。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- parallels|parallels desktop 解决网络初始化失败问题
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- SpringBoot调用公共模块的自定义注解失效的解决
- 解决SpringBoot引用别的模块无法注入的问题
- Spark|Spark 数据倾斜及其解决方案
- 解决SyntaxError:|解决SyntaxError: invalid syntax
- Spectrum|Spectrum 区块偶尔停止同步问题排查与解决笔记
- 一劳永逸地解决词汇量不够的问题
- Hexo代码块前后空白行问题