接上篇,为什么此种模式下,在spring托管CMT管理的JTA事务中,无法切换数据源,忙活了好久,对着日志流程和源代码,貌似问题出现在下面的代码中:
- org.mybatis.spring .SqlSessionUtils
-
- public static SqlSession getSqlSession方法:
-
-
- SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
-
-
- if (holder != null && holder.isSynchronizedWithTransaction()) {
-
- if (logger.isDebugEnabled()) {
- logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
- }
-
- return holder.getSqlSession();
- }
-
-
- if (logger.isDebugEnabled()) {
- logger.debug("Creating SqlSession with JDBC Connection [" + conn + "]");
- }
-
-
- SqlSession session = sessionFactory.openSession(executorType, conn);
-
-
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Registering transaction synchronization for SqlSession [" + session + "]");
- }
-
-
- holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
-
-
- TransactionSynchronizationManager.bindResource(sessionFactory, holder);
- TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
- holder.setSynchronizedWithTransaction(true);
- holder.requested();
-
-
-
- public static void closeSqlSession方法:
- SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
-
-
- if ((holder != null) && (holder.getSqlSession() == session)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Releasing transactional SqlSession [" + session + "]");
- }
- holder.released();
-
-
- public void beforeCommit(boolean readOnly) 方法:
-
-
- if (TransactionSynchronizationManager.isActualTransactionActive()) {
- try {
- if (logger.isDebugEnabled()) {
- logger.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
- }
- this.holder.getSqlSession().commit();
-
-
- public void afterCompletion(int status) 方法:
-
-
- if (!this.holder.isOpen()) {
- TransactionSynchronizationManager.unbindResource(this.sessionFactory);
- try {
- if (logger.isDebugEnabled()) {
- logger.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
- }
- this.holder.getSqlSession().close();
在事务中,mybatis操作两个数据库的步骤流程:
1.创建SqlSession --第一个DAO,操作第一个DB
2.判断当前有事务
3.创建当前session的holder
4.将当前session的sessionFacotry的holder注册到事务中
5.执行sql。。。。
6.holder释放掉当前事务的session
7.当前在事务中,且sessionFactory的holder存在,则取得当前事务的session --第二个DAO,操作第二个DB
8.执行sql。。。。
9.释放掉当前事务的session
10.session提交
11.解除事务绑定的sessionFactory并关闭
可以知道在操作第二个DAO的时候取得的是,在事务中绑定的第一个SqlSession,整个Service用同一个SqlSession,所以无法切换数据源。
问题解决思路:通过上面的源代码
- SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
-
-
- /4.将session的holder注册到事务中
- TransactionSynchronizationManager.bindResource(sessionFactory, holder);
- ransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
- holder.setSynchronizedWithTransaction(true);
可以知道,事务绑定的是mybatis的当前SqlSessionFactory,如果SqlSessionFactory变了,则事务TransactionSynchronizationManager通过SqlSessionFactory(getResource(sessionFactory))获取
的SqlSessionHolder必定不是上一个事务中的,即holder.isSynchronizedWithTransaction()为false。
由此,可以找出一个方法解决,动态切换SqlSessionFactory
OK,代码如下:
-
-
-
-
- @SuppressWarnings("unchecked")
- public class ContextHolder<T> {
-
- private static final ThreadLocal contextHolder = new ThreadLocal();
-
- public static <T> void setContext(T context)
- {
- contextHolder.set(context);
- }
-
- public static <T> T getContext()
- {
- return (T) contextHolder.get();
- }
-
- public static void clearContext()
- {
- contextHolder.remove();
- }
- }
-
-
-
-
-
- public class DynamicSqlSessionDaoSupport extends DaoSupport {
-
- private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
-
- private SqlSessionFactory defaultTargetSqlSessionFactory;
-
- private SqlSession sqlSession;
-
- private boolean externalSqlSession;
-
- @Autowired(required = false)
- public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
- if (!this.externalSqlSession) {
- this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
- }
- }
-
- @Autowired(required = false)
- public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
- this.sqlSession = sqlSessionTemplate;
- this.externalSqlSession = true;
- }
-
-
-
-
-
-
-
-
- public final SqlSession getSqlSession() {
- SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder
- .getContext());
- if (targetSqlSessionFactory != null) {
- setSqlSessionFactory(targetSqlSessionFactory);
- } else if (defaultTargetSqlSessionFactory != null) {
- setSqlSessionFactory(defaultTargetSqlSessionFactory);
- }
- return this.sqlSession;
- }
-
-
-
-
- protected void checkDaoConfig() {
- Assert.notNull(this.sqlSession,
- "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
- }
-
- public Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys() {
- return targetSqlSessionFactorys;
- }
-
-
-
-
-
- public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
- this.targetSqlSessionFactorys = targetSqlSessionFactorys;
- }
-
- public SqlSessionFactory getDefaultTargetSqlSessionFactory() {
- return defaultTargetSqlSessionFactory;
- }
-
-
-
-
-
- public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
- this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
- }
-
- }
-
- public class xxxDaoImpl extends DynamicSqlSessionDaoSupport implements xxxDao {
-
- public int insertUser(User user) {
-
- return this.getSqlSession().insert("xxx.xxxDao.insertUser", user);
- }
-
- }
spring配置如下:
-
- <bean id="ds1" class="org.springframework.jndi.JndiObjectFactoryBean">
- <property name="jndiName">
- <value>testxa</value>
- </property>
- <property name="resourceRef">
- <value>true</value>
- </property>
- </bean>
- <bean id="ds2" class="org.springframework.jndi.JndiObjectFactoryBean">
- <property name="jndiName">
- <value>testxa1</value>
- </property>
- <property name="resourceRef">
- <value>true</value>
- </property>
- </bean>
-
-
-
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="ds1" />
- <property name="configLocation" value="classpath:config/mybatis-config.xml" />
- </bean>
- <bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="ds2" />
- <property name="configLocation" value="classpath:config/mybatis-config.xml" />
- </bean>
-
- <span style="white-space: pre;"> </span>
- <bean id="dynamicSqlSessionDaoSupport" class="com.suning.cmp.common.multidatasource.DynamicSqlSessionDaoSupport">
- <property name="targetSqlSessionFactorys">
- <map value-type="org.apache.ibatis.session.SqlSessionFactory">
- <entry key="ds1" value-ref="sqlSessionFactory" />
- <entry key="ds2" value-ref="sqlSessionFactory1" />
- </map>
- </property>
- <property name="defaultTargetSqlSessionFactory" ref="sqlSessionFactory" />
- </bean>
-
- <bean id="xxxDao" class="xxx.xxxDaoImpl" parent="dynamicSqlSessionDaoSupport"></bean>
Service测试代码如下:
- @Transactional
- public void testXA() {
-
- ContextHolder.setContext("ds1");
-
- xxxDao.insertUser(user);
-
-
- ContextHolder.setContext("ds2");
-
- xxxDao.insertUser(user);
-
-
- }
通过Service代码,每个DAO访问都会调用getSqlSession()方法,此时就会调用DynamicSqlSessionDaoSupport的如下代码:
- public final SqlSession getSqlSession() {
- SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder
- .getContext());
- if (targetSqlSessionFactory != null) {
- setSqlSessionFactory(targetSqlSessionFactory);
- } else if (defaultTargetSqlSessionFactory != null) {
- setSqlSessionFactory(defaultTargetSqlSessionFactory);
- }
- return this.sqlSession;
- }
起到动态切换SqlSessionFactory(每一个SqlSessionFactory对应一个DB)。OK,到此圆满解决,动态切换和事务这两个问题。
在此,我补充下为什么到用到动态切换,其实每一个SqlSessionFactory对应一个DB,而关于此DB操作的所有DAO对应此SqlSessionFactory,在Service中不去切换,直接用对应不同SqlSessionFactory
的DAO也可以,此种方式可以参考附件:《Spring下mybatis多数据源配置》
问题就在于,项目中不同DB存在相同的Table,动态可以做到只配置一个DAO,且操作哪个DB是通过路由Routing或者通过什么获取才能知道(延迟到Service时才知道对应哪个DB),此种情况用到动态切换,就显得方便很多。。。
|