Mybatis+ Spring + JTA

 

最近搭建架构,碰到JTA和事务Transaction的问题,在此做个总结:

 

架构:Mybatis+ Spring

 

技术:spring的AbstractRoutingDataSource和JTA

 

老规矩,先贴代码,在讲原理,刚开始的时候不使用JTA,代码如下:

 

 

Java代码   收藏代码
  1. /** 
  2. * DataSource上下文句柄,通过此类设置需要访问的对应数据源 
  3. * 
  4. */  
  5. public class DataSourceContextHolder {  
  6.   
  7.     /** 
  8.      * DataSource上下文,每个线程对应相应的数据源key 
  9.      */  
  10.     public static final ThreadLocal contextHolder = new ThreadLocal();  
  11.       
  12.     public static void setDataSourceType(String dataSourceType)  
  13.     {  
  14.         contextHolder.set(dataSourceType);  
  15.     }  
  16.       
  17.     public static String getDataSourceType()  
  18.     {  
  19.         return contextHolder.get();  
  20.     }  
  21.       
  22.     public static void clearDataSourceType()  
  23.     {  
  24.         contextHolder.remove();  
  25.     }  
  26. }  

 

 

 

Java代码   收藏代码
  1. /** 
  2. * 动态数据源 
  3. * 
  4. */  
  5. public class DynamicDataSource extends AbstractRoutingDataSource {  
  6.   
  7.     @Override  
  8.     protected Object determineCurrentLookupKey() {  
  9.         return DataSourceContextHolder.getDataSourceType();  
  10.     }  
  11.   
  12. }  

 

 

spring中配置如下:

 

 

Xml代码   收藏代码
  1. <!-- 配置数据源 -->  
  2.         <bean id="ds1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"  
  3.                 lazy-init="false">  
  4.                 <property name="driverClassName" value="${jdbc.ds1.driverClassName}" />  
  5.                 <property name="url" value="${jdbc.ds1.url}" />  
  6.                 <property name="username" value="${jdbc.ds1.username}" />  
  7.                 <property name="password" value="${jdbc.ds1.password}" />  
  8.                 <property name="initialSize" value="5" />  
  9.                 <property name="maxActive" value="10" />  
  10.                 <property name="maxWait" value="60000" />  
  11.                 <property name="poolPreparedStatements" value="true" />  
  12.         </bean>  
  13.   
  14.         <bean id="ds2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"  
  15.                 lazy-init="false">  
  16.                 <property name="driverClassName" value="${jdbc.ds2.driverClassName}" />  
  17.                 <property name="url" value="${jdbc.ds2.url}" />  
  18.                 <property name="username" value="${jdbc.ds2.username}" />  
  19.                 <property name="password" value="${jdbc.ds2.password}" />  
  20.                 <property name="initialSize" value="5" />  
  21.                 <property name="maxActive" value="10" />  
  22.                 <property name="maxWait" value="60000" />  
  23.                 <property name="poolPreparedStatements" value="true" />  
  24.         </bean>  
  25.   
  26.         <!-- 动态数据源 -->  
  27.         <bean id="dataSource" class="xxx.DynamicDataSource">  
  28.                 <property name="targetDataSources">  
  29.                         <map>  
  30.                                 <entry key="ds1" value-ref="ds1" />                                 
  31.                                 <entry key="ds2" value-ref="ds2" />  
  32.                         </map>  
  33.                 </property>  
  34.                 <property name="defaultTargetDataSource" ref="ds1" />  
  35.   
  36.         </bean>  
  37.   
  38.         <!-- 事务管理 -->  
  39.         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  40.                 <property name="dataSource" ref="dataSource" />  
  41.         </bean>  
  42.         <tx:annotation-driven/>  
  43.   
  44.         <!-- myBatis配置 -->  
  45.         <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  46.                 <property name="configLocation" value="classpath:mybatis-config.xml" />  
  47.                 <property name="dataSource" ref="dataSource" />  
  48.         </bean>  
  49.   
  50. <span style="white-space: pre;">        </span><!-- DAO层由 MapperScannerConfigurer自动生成mapper bean -->  
  51.         <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  52.                 <property name="basePackage" value="xxx.mapper" />  
  53.         </bean>  

 

因为每个Service目前只可能访问一个DataSource,所以在调用Service的时候,调用DataSourceContextHolder.setDataSourceType(key)(key可以为ds1,ds2),

就可以动态切换数据源了当然最好用AOP思想,技术上spring + AspectJ,在每个Service需要的方法切上一刀),

而且对于spring的@Transactional事务管理是起作用的

 

 

OK,按照这种模式,如果Service可能访问多个库,就将DataSourceTransactionManager换成JtaTransactionManager

 

 

Xml代码   收藏代码
  1. <bean id="transactionManager"  
  2.         class="org.springframework.transaction.jta.JtaTransactionManager" />  
  3.     <tx:annotation-driven transaction-manager="transactionManager" />  

 

当然,Datasource换成JNDI获取

 

 

Xml代码   收藏代码
  1. <!-- 创建数据源。 -->  
  2.     <bean id="ds1" class="org.springframework.jndi.JndiObjectFactoryBean">  
  3.         <property name="jndiName">  
  4.             <value>ds1</value>  
  5.         </property>  
  6.         <property name="resourceRef">  
  7.             <value>true</value>  
  8.         </property>  
  9.     </bean>  
  10.     <bean id="ds2" class="org.springframework.jndi.JndiObjectFactoryBean">  
  11.         <property name="jndiName">  
  12.             <value>ds2</value>  
  13.         </property>  
  14.         <property name="resourceRef">  
  15.             <value>true</value>  
  16.         </property>  
  17.     </bean>   

 

spring的@Transactional事务管理中,那是死活无法切换数据源

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

 

 

Java代码   收藏代码
  1. org.mybatis.spring .SqlSessionUtils  
  2.   
  3. public static SqlSession getSqlSession方法:  
  4.   
  5.   
  6. SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);  
  7.         
  8. //7.当前在事务中,且session的holder存在,则取得当前事务的session  
  9. if (holder != null && holder.isSynchronizedWithTransaction()) {  
  10.               
  11.             if (logger.isDebugEnabled()) {  
  12.                 logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");  
  13.             }  
  14.   
  15.             return holder.getSqlSession();  
  16.         }  
  17.   
  18.   
  19. if (logger.isDebugEnabled()) {  
  20.             logger.debug("Creating SqlSession with JDBC Connection [" + conn + "]");  
  21. }  
  22.   
  23. //1.创建SqlSession  
  24. SqlSession session = sessionFactory.openSession(executorType, conn);  
  25.   
  26. //2.判断当前有事务  
  27. if (TransactionSynchronizationManager.isSynchronizationActive()) {  
  28.             if (logger.isDebugEnabled()) {  
  29.                 logger.debug("Registering transaction synchronization for SqlSession [" + session + "]");  
  30.             }  
  31.   
  32. //3.创建当前session的holder  
  33.             holder = new SqlSessionHolder(session, executorType, exceptionTranslator);  
  34.   
  35. //4.将session的holder注册到事务中             
  36.  TransactionSynchronizationManager.bindResource(sessionFactory, holder);              
  37. TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));  
  38.             holder.setSynchronizedWithTransaction(true);  
  39.             holder.requested();  
  40.   
  41. //5.(8.)执行sql。。。。  
  42.   
  43. public static void closeSqlSession方法:  
  44. SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);  
  45.   
  46. //6.(9.)释放掉当前事务的session  
  47. if ((holder != null) && (holder.getSqlSession() == session)) {  
  48.      if (logger.isDebugEnabled()) {  
  49. logger.debug("Releasing transactional SqlSession [" + session + "]");  
  50.      }  
  51. holder.released();  
  52.   
  53.   
  54. public void beforeCommit(boolean readOnly) 方法:  
  55.   
  56. //10.session提交   
  57. if (TransactionSynchronizationManager.isActualTransactionActive()) {  
  58.                 try {  
  59.                     if (logger.isDebugEnabled()) {  
  60.                         logger.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");  
  61.                     }  
  62.                     this.holder.getSqlSession().commit();  
  63.    
  64.   
  65. public void afterCompletion(int status) 方法:  
  66.   
  67. //11.解除事务绑定的session并关闭  
  68.             if (!this.holder.isOpen()) {  
  69.                 TransactionSynchronizationManager.unbindResource(this.sessionFactory);  
  70.                 try {  
  71.                     if (logger.isDebugEnabled()) {  
  72.                         logger.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");  
  73.                     }  
  74.                     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,所以无法切换数据源。

 

问题解决思路:通过上面的源代码

 

 

Java代码   收藏代码
  1. SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);  
  2.   
  3.         
  4. /4.将session的holder注册到事务中             
  5. TransactionSynchronizationManager.bindResource(sessionFactory, holder);              
  6. ransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));  
  7.            holder.setSynchronizedWithTransaction(true);  

 

 

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

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

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

OK,代码如下:

 

 

 

Java代码   收藏代码
  1. /** 
  2.  * 上下文Holder 
  3.  * 
  4.  */  
  5. @SuppressWarnings("unchecked")   
  6. public class ContextHolder<T> {  
  7.   
  8.     private static final ThreadLocal contextHolder = new ThreadLocal();  
  9.       
  10.     public static <T> void setContext(T context)  
  11.     {  
  12.         contextHolder.set(context);  
  13.     }  
  14.       
  15.     public static <T> T getContext()  
  16.     {  
  17.         return (T) contextHolder.get();  
  18.     }  
  19.       
  20.     public static void clearContext()  
  21.     {  
  22.         contextHolder.remove();  
  23.     }  
  24. }  

 

 

 

 

Java代码   收藏代码
  1. /** 
  2.  * 动态切换SqlSessionFactory的SqlSessionDaoSupport 
  3.  * 
  4.  * @see org.mybatis.spring.support.SqlSessionDaoSupport 
  5.  */  
  6. public class DynamicSqlSessionDaoSupport extends DaoSupport {  
  7.   
  8.     private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;  
  9.   
  10.     private SqlSessionFactory              defaultTargetSqlSessionFactory;  
  11.   
  12.     private SqlSession                     sqlSession;  
  13.   
  14.     private boolean                        externalSqlSession;  
  15.   
  16.     @Autowired(required = false)  
  17.     public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {  
  18.         if (!this.externalSqlSession) {  
  19.             this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);  
  20.         }  
  21.     }  
  22.   
  23.     @Autowired(required = false)  
  24.     public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {  
  25.         this.sqlSession = sqlSessionTemplate;  
  26.         this.externalSqlSession = true;  
  27.     }  
  28.   
  29.     /** 
  30.      * Users should use this method to get a SqlSession to call its statement 
  31.      * methods This is SqlSession is managed by spring. Users should not 
  32.      * commit/rollback/close it because it will be automatically done. 
  33.      *  
  34.      * @return Spring managed thread safe SqlSession 
  35.      */  
  36.     public final SqlSession getSqlSession() {  
  37.         SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder  
  38.                 .getContext());  
  39.         if (targetSqlSessionFactory != null) {  
  40.             setSqlSessionFactory(targetSqlSessionFactory);  
  41.         } else if (defaultTargetSqlSessionFactory != null) {  
  42.             setSqlSessionFactory(defaultTargetSqlSessionFactory);  
  43.         }  
  44.         return this.sqlSession;  
  45.     }  
  46.   
  47.     /** 
  48.      * {@inheritDoc} 
  49.      */  
  50.     protected void checkDaoConfig() {  
  51.         Assert.notNull(this.sqlSession,  
  52.                 "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");  
  53.     }  
  54.   
  55.     public Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys() {  
  56.         return targetSqlSessionFactorys;  
  57.     }  
  58.   
  59.     /** 
  60.      * Specify the map of target SqlSessionFactory, with the lookup key as key. 
  61.      * @param targetSqlSessionFactorys 
  62.      */  
  63.     public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {  
  64.         this.targetSqlSessionFactorys = targetSqlSessionFactorys;  
  65.     }  
  66.   
  67.     public SqlSessionFactory getDefaultTargetSqlSessionFactory() {  
  68.         return defaultTargetSqlSessionFactory;  
  69.     }  
  70.   
  71.     /** 
  72.      * Specify the default target SqlSessionFactory, if any. 
  73.      * @param defaultTargetSqlSessionFactory 
  74.      */  
  75.     public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {  
  76.         this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;  
  77.     }  
  78.   
  79. }  

 

 

 

Java代码   收藏代码
  1. //每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport  
  2. public class xxxDaoImpl extends DynamicSqlSessionDaoSupport implements xxxDao {  
  3.   
  4.     public int insertUser(User user) {  
  5.           
  6.         return this.getSqlSession().insert("xxx.xxxDao.insertUser", user);  
  7.     }  
  8.   
  9. }  

 

 

 

spring配置如下:

 

 

Xml代码   收藏代码
  1. <!-- 创建数据源。 -->  
  2.     <bean id="ds1" class="org.springframework.jndi.JndiObjectFactoryBean">  
  3.         <property name="jndiName">  
  4.             <value>testxa</value>  
  5.         </property>  
  6.         <property name="resourceRef">  
  7.             <value>true</value>  
  8.         </property>  
  9.     </bean>  
  10.     <bean id="ds2" class="org.springframework.jndi.JndiObjectFactoryBean">  
  11.         <property name="jndiName">  
  12.             <value>testxa1</value>  
  13.         </property>  
  14.         <property name="resourceRef">  
  15.             <value>true</value>  
  16.         </property>  
  17.     </bean>   
  18.   
  19.   
  20. <!-- sqlSessionFactory工厂 -->  
  21.     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  22.         <property name="dataSource" ref="ds1" />  
  23.         <property name="configLocation" value="classpath:config/mybatis-config.xml" />  
  24.     </bean>  
  25.     <bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean">  
  26.         <property name="dataSource" ref="ds2" />  
  27.         <property name="configLocation" value="classpath:config/mybatis-config.xml" />  
  28.     </bean>  
  29.   
  30. <span style="white-space: pre;">    </span><!-- 动态切换SqlSessionFactory  -->  
  31.     <bean id="dynamicSqlSessionDaoSupport" class="com.suning.cmp.common.multidatasource.DynamicSqlSessionDaoSupport">  
  32.         <property name="targetSqlSessionFactorys">  
  33.             <map value-type="org.apache.ibatis.session.SqlSessionFactory">  
  34.                 <entry key="ds1" value-ref="sqlSessionFactory" />  
  35.                 <entry key="ds2" value-ref="sqlSessionFactory1" />  
  36.             </map>  
  37.         </property>  
  38.         <property name="defaultTargetSqlSessionFactory" ref="sqlSessionFactory" />  
  39.     </bean>  
  40.   
  41.     <bean id="xxxDao" class="xxx.xxxDaoImpl" parent="dynamicSqlSessionDaoSupport"></bean>  

 

 

 

Service测试代码如下:

 

 

Java代码   收藏代码
  1. @Transactional  
  2.     public void testXA() {  
  3.   
  4.         ContextHolder.setContext("ds1");  
  5.   
  6.         xxxDao.insertUser(user);  
  7.   
  8.           
  9.         ContextHolder.setContext("ds2");  
  10.   
  11.         xxxDao.insertUser(user);  
  12.           
  13.           
  14.     }  

 

 

 

通过Service代码,每个DAO访问都会调用getSqlSession()方法,此时就会调用DynamicSqlSessionDaoSupport的如下代码

 

 

Java代码   收藏代码
  1. public final SqlSession getSqlSession() {  
  2.         SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder  
  3.                 .getContext());  
  4.         if (targetSqlSessionFactory != null) {  
  5.             setSqlSessionFactory(targetSqlSessionFactory);  
  6.         } else if (defaultTargetSqlSessionFactory != null) {  
  7.             setSqlSessionFactory(defaultTargetSqlSessionFactory);  
  8.         }  
  9.         return this.sqlSession;  
  10.     }  

 

 

起到动态切换SqlSessionFactory(每一个SqlSessionFactory对应一个DB)。OK,到此圆满解决,动态切换和事务这两个问题。

在此,我补充下为什么到用到动态切换,其实每一个SqlSessionFactory对应一个DB,而关于此DB操作的所有DAO对应此SqlSessionFactory,在Service中不去切换,直接用对应不同SqlSessionFactory

的DAO也可以,此种方式可以参考附件:《Spring下mybatis多数据源配置》

问题就在于,项目中不同DB存在相同的Table,动态可以做到只配置一个DAO,且操作哪个DB是通过路由Routing或者通过什么获取才能知道(延迟到Service时才知道对应哪个DB),此种情况用到动态切换,就显得方便很多。。。

你可能感兴趣的:(spring)