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

[经验分享] 多库数据源深入分析(Mybatis+ Spring + JTA)(二)

[复制链接]

尚未签到

发表于 2016-11-27 11:03:31 | 显示全部楼层 |阅读模式
  接上篇,为什么此种模式下,在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),此种情况用到动态切换,就显得方便很多。。。

运维网声明 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-306127-1-1.html 上篇帖子: MyBatis Generator 生成注解还是xml的方式,去除多余注释 下篇帖子: 关于Spring3.x + MyBatis 3.x架构的一点经验
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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