spring事务在多库下的处理

这一章主要介绍,分布式事务的雏形-spring事务在多库下的处理。这个算是比较难得并且很容易在开发过程中遇到各种坑。介绍之前先讲一下问题的缘由,前一阵,一个朋友说他在做一个业务,需要先从A库捞取一些数据,然后再B库里面根据对A库数据的处理来决定是否插入一条数据,在测试的时候测了回滚的情况,但是死活回滚不成功,后来在架构师的帮助下解决了。解决方法是从新配置一个事务管理器,这个东西引起了我的注意,因为我觉得如果在开事务的时候先切数据源应该不会这么麻烦,带着这个问题来探讨。

143321_ZjHn_3203060.png

要清楚地看到问题,就必须看一下spring怎么保证从事务中获取的链接是同一个。核心其实就是一个线程局部变量,然后放了一个map,map的key : datasource,value:connection包装类,至于这么做的原因:spring保证多个数据源依然可以在事务中准确获取链接。

那回到那个问题,切数据源为什么回滚无效?假设不在@transactional(value="xxx")配置事务管理器,那么事务管理器一开始会用的默认数据源。类似下图。那么此时在取链接的时候,会看下当前是否存在事务,不存在直接提交,如果存在,那么会拿到datasource,从map中取链接,但是很遗憾,此时的数据源已经不是事务管理器中的那个数据源了,所以取不到。如果配置了事务管理器,那么一切就好办了。

bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 数据源
    dataSource在applicationContext-dao.xml中配置了
     -->
    <property name="dataSource" ref="dataSource"/>
</bean>

 

带着这个问题,假设场景变一下,有A库和B库,在A,B库同时插入数据,有一个出现异常就会滚,怎么解决?解决方案看似很多,但是都和事务管理器有关。在探讨这个问题的时候也让我一直存在疑惑的到底是用mybatis的接口好还是原生dao好,在解决分布式事务问题,最好用编程式事务+原生dao,不然好多错根本跟踪不到,直接上代码。

public class CommonSqlsession {
    private SqlSession sqlSession;

    @Resource(name = "sqlSessionFactory")
    public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }

    public final SqlSession getSqlSession() {
        return this.sqlSession;
    }
}
@Repository
public class UserDaoImpl extends CommonSqlsession implements UserDao {
    public int insert(User user) throws Exception {
        return getSqlSession().insert("com.elin4it.ssm.mapper.mybatis.UserMapper.insert", user);
    }
}
@Repository("seoFundDao")
public class SeoFundDaoImpl extends CommonSqlsession1 implements SeoFundDao {
    public int insert(TbFundSeoRecord seoRecord) throws Exception {
        return getSqlSession().insert("com.elin4it.ssm.mapper.test.TbFundSeoRecordMapper.insert", seoRecord);
    }
}
@Resource
private DataSourceTransactionManager transactionManager;

@Resource
private DataSourceTransactionManager transactionManager1;
/**
     * @param record
     * @param user
     * @return
     */
    public int insertFacade1(TbFundSeoRecord record, User user) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus status = transactionManager.getTransaction(def);
        TransactionStatus status1 = transactionManager1.getTransaction(def);

        try {
            int count1 = userDao.insert(user);
//            int i = 1 / 0;
            int count2 = seoFundDao.insert(record);

            if (count1 > 0 && count2 > 0) {
                transactionManager.commit(status);
                transactionManager1.commit(status1);
            } else {
                throw new Exception("有一个未提交成功 count1=" + count1 + ",count2=" + count2);
            }
        } catch (Exception e) {
            e.printStackTrace();
            /**
             * spring提交事务按照stack排序,先入后出
             */
            transactionManager1.rollback(status1);
            transactionManager.rollback(status);
        }

        return 0;
    }

总的思路是这样的,两个事务管理器,然后从不同数据源取session,这样spring在处理事务的时候会建立一个map在ThreadLocal中,然后设置两个key,分别为ds1 : con1,ds2:con2,但是这真的是可以实现分布式事务吗?

145248_NTeG_3203060.png

145252_eIBD_3203060.png

通过上面两个图,可以发现,如果在commit或者rollback的时候出问题,假设第一个事务提交成功,第二个事务提交的时候宕机了,那么就会导致第一个库中数据落地,第二个库没有数据。虽然commit很快,但是依然没法100%保证分布式事务提交。所以真的在工作中要操作多库的提交一致性还是借助JTA这种分布式框架利用2PC来解决吧或者通过业务分类,保证一致性提交的表都在一个库中。

转载于:https://my.oschina.net/u/3203060/blog/850445

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值