多库数据源深入分析(Mybatis+ Spring + JTA)(二)

接上篇,为什么此种模式下,在spring托管CMT管理的JTA事务中,无法切换数据源,忙活了好久,对着日志流程和源代码,貌似问题出现在下面的代码中:

 


org.mybatis.spring .SqlSessionUtils

public static SqlSession getSqlSession方法:


SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
      
//7.当前在事务中,且session的holder存在,则取得当前事务的session
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 + "]");
}

//1.创建SqlSession
SqlSession session = sessionFactory.openSession(executorType, conn);

//2.判断当前有事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Registering transaction synchronization for SqlSession [" + session + "]");
            }

//3.创建当前session的holder
            holder = new SqlSessionHolder(session, executorType, exceptionTranslator);

//4.将session的holder注册到事务中           
 TransactionSynchronizationManager.bindResource(sessionFactory, holder);            
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
            holder.setSynchronizedWithTransaction(true);
            holder.requested();

//5.(8.)执行sql。。。。

public static void closeSqlSession方法:
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

//6.(9.)释放掉当前事务的session
if ((holder != null) && (holder.getSqlSession() == session)) {
     if (logger.isDebugEnabled()) {
logger.debug("Releasing transactional SqlSession [" + session + "]");
     }
holder.released();


public void beforeCommit(boolean readOnly) 方法:

//10.session提交 
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) 方法:

//11.解除事务绑定的session并关闭
            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.创建当前sessionholder

4.将当前session的sessionFacotryholder注册到事务中

5.执行sql。。。。

6.holder释放掉当前事务的session

7.当前在事务中,且sessionFactoryholder存在,则取得当前事务的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);            
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
            holder.setSynchronizedWithTransaction(true);

 

 

可以知道,事务绑定的是mybatis的当前SqlSessionFactory,如果SqlSessionFactory变了,则事务TransactionSynchronizationManager通过SqlSessionFactory(getResource(sessionFactory))获取

的SqlSessionHolder必定不是上一个事务中的,即holder.isSynchronizedWithTransaction()为false

由此,可以找出一个方法解决,动态切换SqlSessionFactory

OK,代码如下:

 

 

/**
 * 上下文Holder
 *
 */
@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();
    }
}
 

 

 

 

/**
 * 动态切换SqlSessionFactory的SqlSessionDaoSupport
 *
 * @see org.mybatis.spring.support.SqlSessionDaoSupport
 */
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;
    }

    /**
     * Users should use this method to get a SqlSession to call its statement
     * methods This is SqlSession is managed by spring. Users should not
     * commit/rollback/close it because it will be automatically done.
     * 
     * @return Spring managed thread safe SqlSession
     */
    public final SqlSession getSqlSession() {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder
                .getContext());
        if (targetSqlSessionFactory != null) {
            setSqlSessionFactory(targetSqlSessionFactory);
        } else if (defaultTargetSqlSessionFactory != null) {
            setSqlSessionFactory(defaultTargetSqlSessionFactory);
        }
        return this.sqlSession;
    }

    /**
     * {@inheritDoc}
     */
    protected void checkDaoConfig() {
        Assert.notNull(this.sqlSession,
                "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
    }

    public Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys() {
        return targetSqlSessionFactorys;
    }

    /**
     * Specify the map of target SqlSessionFactory, with the lookup key as key.
     * @param targetSqlSessionFactorys
     */
    public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
        this.targetSqlSessionFactorys = targetSqlSessionFactorys;
    }

    public SqlSessionFactory getDefaultTargetSqlSessionFactory() {
        return defaultTargetSqlSessionFactory;
    }

    /**
     * Specify the default target SqlSessionFactory, if any.
     * @param defaultTargetSqlSessionFactory
     */
    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
    }

}

 

 

 

//每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport
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> 


<!-- sqlSessionFactory工厂 -->
	<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>

	<!-- 动态切换SqlSessionFactory  -->
	<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),此种情况用到动态切换,就显得方便很多。。。

 

 

 

 

 

 

 

你可能感兴趣的:(jta,多数据源,多库)