这一章主要介绍,分布式事务的雏形-spring事务在多库下的处理。这个算是比较可贵而且很容易在开发过程当中遇到各类坑。介绍以前先讲一下问题的原因,前一阵,一个朋友说他在作一个业务,须要先从A库捞取一些数据,而后再B库里面根据对A库数据的处理来决定是否插入一条数据,在测试的时候测了回滚的状况,可是死活回滚不成功,后来在架构师的帮助下解决了。解决方法是重新配置一个事务管理器,这个东西引发了个人注意,由于我以为若是在开事务的时候先切数据源应该不会这么麻烦,带着这个问题来探讨。spring
要清楚地看到问题,就必须看一下spring怎么保证从事务中获取的连接是同一个。核心其实就是一个线程局部变量,而后放了一个map,map的key : datasource,value:connection包装类,至于这么作的缘由:spring保证多个数据源依然能够在事务中准确获取连接。sql
那回到那个问题,切数据源为何回滚无效?假设不在@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,否则好多错根本跟踪不到,直接上代码。session
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,可是这真的是能够实现分布式事务吗?mybatis
经过上面两个图,能够发现,若是在commit或者rollback的时候出问题,假设第一个事务提交成功,第二个事务提交的时候宕机了,那么就会致使第一个库中数据落地,第二个库没有数据。虽然commit很快,可是依然无法100%保证分布式事务提交。因此真的在工做中要操做多库的提交一致性仍是借助JTA这种分布式框架利用2PC来解决吧或者经过业务分类,保证一致性提交的表都在一个库中。架构