设为首页 收藏本站
查看: 1087|回复: 0

[经验分享] Mybatis+ Spring + JTA

[复制链接]

尚未签到

发表于 2016-11-24 10:07:38 | 显示全部楼层 |阅读模式
多库数据源深入分析(Mybatis+ Spring + JTA)(一)
博客分类:

  • java
多库多数据源AbstractRoutingDataSourceJTA 

  最近搭建架构,碰到JTA和事务Transaction的问题,在此做个总结:
  架构:Mybatis+ Spring
  技术:spring的AbstractRoutingDataSource和JTA
  老规矩,先贴代码,在讲原理,刚开始的时候不使用JTA,代码如下:
Java代码   DSC0000.png


  • /** 
  • * DataSource上下文句柄,通过此类设置需要访问的对应数据源 

  • */  
  • public class DataSourceContextHolder {  
  •   
  •     /** 
  •      * DataSource上下文,每个线程对应相应的数据源key 
  •      */  
  •     public static final ThreadLocal contextHolder = new ThreadLocal();  
  •       
  •     public static void setDataSourceType(String dataSourceType)  
  •     {  
  •         contextHolder.set(dataSourceType);  
  •     }  
  •       
  •     public static String getDataSourceType()  
  •     {  
  •         return contextHolder.get();  
  •     }  
  •       
  •     public static void clearDataSourceType()  
  •     {  
  •         contextHolder.remove();  
  •     }  
  • }  

Java代码  


  • /** 
  • * 动态数据源 

  • */  
  • public class DynamicDataSource extends AbstractRoutingDataSource {  
  •   
  •     @Override  
  •     protected Object determineCurrentLookupKey() {  
  •         return DataSourceContextHolder.getDataSourceType();  
  •     }  
  •   
  • }  

  spring中配置如下:
Xml代码  


  • <!-- 配置数据源 -->  
  •         <bean id="ds1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"  
  •                 lazy-init="false">  
  •                 <property name="driverClassName" value="${jdbc.ds1.driverClassName}" />  
  •                 <property name="url" value="${jdbc.ds1.url}" />  
  •                 <property name="username" value="${jdbc.ds1.username}" />  
  •                 <property name="password" value="${jdbc.ds1.password}" />  
  •                 <property name="initialSize" value="5" />  
  •                 <property name="maxActive" value="10" />  
  •                 <property name="maxWait" value="60000" />  
  •                 <property name="poolPreparedStatements" value="true" />  
  •         </bean>  
  •   
  •         <bean id="ds2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"  
  •                 lazy-init="false">  
  •                 <property name="driverClassName" value="${jdbc.ds2.driverClassName}" />  
  •                 <property name="url" value="${jdbc.ds2.url}" />  
  •                 <property name="username" value="${jdbc.ds2.username}" />  
  •                 <property name="password" value="${jdbc.ds2.password}" />  
  •                 <property name="initialSize" value="5" />  
  •                 <property name="maxActive" value="10" />  
  •                 <property name="maxWait" value="60000" />  
  •                 <property name="poolPreparedStatements" value="true" />  
  •         </bean>  
  •   
  •         <!-- 动态数据源 -->  
  •         <bean id="dataSource" class="xxx.DynamicDataSource">  
  •                 <property name="targetDataSources">  
  •                         <map>  
  •                                 <entry key="ds1" value-ref="ds1" />                                 
  •                                 <entry key="ds2" value-ref="ds2" />  
  •                         </map>  
  •                 </property>  
  •                 <property name="defaultTargetDataSource" ref="ds1" />  
  •   
  •         </bean>  
  •   
  •         <!-- 事务管理 -->  
  •         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  •                 <property name="dataSource" ref="dataSource" />  
  •         </bean>  
  •         <tx:annotation-driven/>  
  •   
  •         <!-- myBatis配置 -->  
  •         <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  •                 <property name="configLocation" value="classpath:mybatis-config.xml" />  
  •                 <property name="dataSource" ref="dataSource" />  
  •         </bean>  
  •   
  • <span style="white-space: pre;">        </span><!-- DAO层由 MapperScannerConfigurer自动生成mapper bean -->  
  •         <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  •                 <property name="basePackage" value="xxx.mapper" />  
  •         </bean>  

  因为每个Service目前只可能访问一个DataSource,所以在调用Service的时候,调用DataSourceContextHolder.setDataSourceType(key)(key可以为ds1,ds2),
  就可以动态切换数据源了(当然最好用AOP思想,技术上spring + AspectJ,在每个Service需要的方法切上一刀),
  而且对于spring的@Transactional事务管理是起作用的
  OK,按照这种模式,如果Service可能访问多个库,就将DataSourceTransactionManager换成JtaTransactionManager
Xml代码  


  • <bean id="transactionManager"  
  •         class="org.springframework.transaction.jta.JtaTransactionManager" />  
  •     <tx:annotation-driven transaction-manager="transactionManager" />  

  当然,Datasource换成JNDI获取
Xml代码  


  • <!-- 创建数据源。 -->  
  •     <bean id="ds1" class="org.springframework.jndi.JndiObjectFactoryBean">  
  •         <property name="jndiName">  
  •             <value>ds1</value>  
  •         </property>  
  •         <property name="resourceRef">  
  •             <value>true</value>  
  •         </property>  
  •     </bean>  
  •     <bean id="ds2" class="org.springframework.jndi.JndiObjectFactoryBean">  
  •         <property name="jndiName">  
  •             <value>ds2</value>  
  •         </property>  
  •         <property name="resourceRef">  
  •             <value>true</value>  
  •         </property>  
  •     </bean>   

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


  • 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,所以无法切换数据源。
  问题解决思路:通过上面的源代码
Java代码  


  • 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,代码如下:
Java代码  


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

Java代码  


  • /** 
  •  * 动态切换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;  
  •     }  
  •   
  • }  

Java代码  


  • //每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport  
  • public class xxxDaoImpl extends DynamicSqlSessionDaoSupport implements xxxDao {  
  •   
  •     public int insertUser(User user) {  
  •           
  •         return this.getSqlSession().insert("xxx.xxxDao.insertUser", user);  
  •     }  
  •   
  • }  

  spring配置如下:
Xml代码  


  • <!-- 创建数据源。 -->  
  •     <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>  
  •   
  • <span style="white-space: pre;">    </span><!-- 动态切换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测试代码如下:
Java代码  


  • @Transactional  
  •     public void testXA() {  
  •   
  •         ContextHolder.setContext("ds1");  
  •   
  •         xxxDao.insertUser(user);  
  •   
  •           
  •         ContextHolder.setContext("ds2");  
  •   
  •         xxxDao.insertUser(user);  
  •           
  •           
  •     }  

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


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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-304853-1-1.html 上篇帖子: Mybatis 简明教程(一) 下篇帖子: Mybatis与Ibatis比较
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表