spring 非分布式 多数据源强事务尝试解决方案

spring 非分布式 多数据源强事务尝试解决方案

背景

springboot项目中用到了多数据源,但是框架又没有采用分布式的,导致不能使用市场上的分布式事务解决方案来解决多数据源的事务问题,但是现在问题要得到解决,没办法,网上找了很多资料,将最终的解决方案分享给大家,实际使用的话比较繁琐,有更好的解决方案,欢迎分享。

常见问题

1. 为什么在一个事务管理器里面不能做到切换数据源(切数据源失败)
事务众所周知,有四大特性ACID,但是大部分人都忘了一个前置条件,就是这里的事务是存在一个单体数据库里面,也就是这里的事务只能在A数据库里面存在,或者B数据库里面存在,也就是事务是一个数据库里面单次操作集的单位,而不是单次操作集就是这里的事务,画一个图更好理解,如下:

spring多数据源数据库与事务之间的关系
所以,在 @Transation 注解的类或方法中切数据源失败的根本原因就是spring已经做了切库操作,并且在@transation注解的加持下已经获取到了第一个切库操作的事务管理器,在第一个数据库的事务管理器内是做不到切到第二个数据库获取第二个数据库的事务管理器的,所以会失败,看上面的图就很明白了,所以如果你把@transation注解加到controller层里面,切库也会失败。

示例代码

	/**
	* 基本思路就是spring切库后会把对应的会话等信息也做对应的调整,所以从IOC容器中sqlSessiionFactory获取切库后的session
	* 并保存到对象中,就可以同时存在多个数据库的事务,这里再次说明了单例下的sqlSessionFactory不能同时存两个数据库的会话信息,
	* 然后通过手动控制事务的形式,做到多数据库的强事务。
	*/
	

	@Autowired
    private SqlSessionFactory sqlSessionFactory;

	@RequestMapping("/tsetTransaction")
    @ResponseBody
    public AjaxMessageEntity testTransaction(){
        AjaxMessageEntity ajaxMessageEntity = new AjaxMessageEntity();
        SqlSession sqlSession = null;
        SqlSession sqlSession2 = null;
        Connection connection = null;
        Connection connection2 = null;
        Savepoint savepoint = null;
        Savepoint savepoint2 = null;
        try {
        	//切换到默认库
            switchDB.change(ControllerCode.DB_DEFAULT);
            //获取到默认库不自动提交的会话
            sqlSession = sqlSessionFactory.openSession(false);
            //获取会话连接
            connection = sqlSession.getConnection();
            //设置会话连接为非自动提交
            connection.setAutoCommit(false);
            //设置会话连接的回滚点
            savepoint = connection.setSavepoint();
            ChargesRecord chargesRecord = new ChargesRecord();
            chargesRecord.setId("100000000000000032410000015554917611541304205");
            chargesRecord.setUpdatedate(DateUtil.getCurDateTime());
            //默认数据库的数据库操作一
            sqlSession.update("com.iss.base.operation.charges.dao.mapper.ChargesRecordMapper.updateByPrimaryKey",chargesRecord);
            chargesRecord.setUpdatedate(null);
            chargesRecord.setInputdate(DateUtil.getCurDateTime());
            //默认数据库的数据库操作二
            sqlSession.update("com.iss.base.operation.charges.dao.mapper.ChargesRecordMapper.updateByPrimaryKey",chargesRecord);
			//切到当前登录用户所在的数据库
            switchDB.change(LoginUserContext.getLoginUserInfo().getDbKye());
            //同上
            sqlSession2 = sqlSessionFactory.openSession(false);
            connection2 = sqlSession2.getConnection();
            connection2.setAutoCommit(false);
            savepoint2 = connection2.setSavepoint();
            SysMenuBO sysMenuBO = new SysMenuBO();
            sysMenuBO.setId("100000000000000430910000015476185521051640339");
            sysMenuBO.setInputDate(DateUtil.getCurDateTime());
            sqlSession2.update("com.iss.base.system.dao.mapper.SysMenuMapper.updateByPrimaryKey",sysMenuBO);
            if(true){
            	//测试异常
                throw new RuntimeException();
            }
            //提交会话连接一操作
            connection.commit();
            //提交会话连接一操作
            connection2.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //异常手动回滚
            if(connection != null){
                try {
                    connection.rollback(savepoint);
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            if(connection2 != null){
                try {
                    connection2.rollback(savepoint2);
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
        }finally {
        	//关闭会话(很重要)
            if(sqlSession != null){
                sqlSession.close();
            }
            if(sqlSession2 != null){
                sqlSession2.close();
            }
        }
        return ajaxMessageEntity;
    }

由于单数据库的事务比较容易,可以用代理模式做封装,这里多数据源的事务操作嵌入了业务代码比较深,目前没有想到什么比较好的形式做进一步的封装,实际应用比较繁琐,如有好的封装方法,请指正,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值