接上篇,为什么此种模式下,在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.创建当前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);
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),此种情况用到动态切换,就显得方便很多。。。
分享到:
相关推荐
springboot + mybatis-plus + database+ 多数据源 + redis + hutool 框架干净,没有其他冗余的成分; 配置了MP的代码生成器,意见生成代码,节省开发时间! 可用于各种定时任务处理,各种夸库操作, 多数据源支持...
spring3+hibernate4+maven+junit 多库多数据源实现,非常好的一个学习多数据分点管理,分别连接不同类型数据源的方法
什么时候才用的到配置多数据源??当我们用不同数据库存放不同数据的时候。这个时候我们整个工程并不是只用一个数据库,所以,要配置多个,在具体的场景应用具体的数据源。
spring mybatis atomikos 分布式事务 自己写的小demo 包含依赖包
springboot多数据源切换mysql+sql server,该事例代码demo,下载下来就能使用,使用工具idea,自行安装mysql和sql server
纯spring多数据库事务,是spring和atomikos组合使用。有spring注解设置数据源,由atomikos负责数据源管理,负责事务管理。追求零配置。大大简化工作量。供参考
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。支持数据库敏感配置信息 加密(可自定义) ENC()。支持每个数据库独立初始化表结构schema和数据库database。支持无数据源启动,支持懒加载...
springMVC+ansj中文分词+多线程+mybatis+redis+多库等等
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...
一个基于springboot的快速集成多数据源的启动器简介dynamic-datasource-spring-boot-starter是一个基于springboot的快速集成多数据源的启动器。其支持Jdk 1.7 +,SpringBoot 1.4.x 1.5.x 2.xx。文件| 文献资料|特性...
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...
springboot整合多数据源 实现多库读写的例子 包含源码
NULL 博文链接:https://wiselyman.iteye.com/blog/1150495
springboot双数据库定时同步,可以是mysql,SqlServer
介绍多库系统和数据库系统的概念、方法、理论
##多数据源动态切换 #####支持数据源分组和简单的负载均衡(轮询) 作者:kyrin(云中鹤) ##项目简介 在公司开发的过程中,用到主从库的切换,于是就想自己动手实现一个可以实现多库之间的随意切 换。于是该项目就...
文档中方案不能彻底解决多库分布式事务数据一致性问题,但能大大降低数据不一致的概率,带来的副作用是数据库连接占用时间会增长,吞吐量会降低。对于一致性与吞吐量的折衷,还需要业务架构师谨慎权衡折衷。
金多库管软件 不打会用 专业人士玩去吧
多库俄罗斯方块C#版本, 有注释,呵呵。
金多库管王5000是目前国内最佳企业级仓库管理系统。界面采用联想输入设计、输入速度快、操作灵活。软件具有商品、供应商、客户支持分类管理功能。 金多库管王5000功能特点 1. 是目前国内最佳企业级仓库管理系统。 ...