接上篇,为什么此种模式下,在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 多库多数据源实现"是一个典型的Java Web项目配置,它涉及了多个核心技术来处理复杂的数据管理需求。下面将详细阐述这些技术以及如何协同工作以实现多库多数据源。 首先,Spring...
myBatis 多数据源配置就是为了解决这样的问题。myBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。与 Spring Boot 结合使用时,可以方便地管理和切换不同的数据源。 首先,我们要理解什么...
然后,对于MyBatis,我们需要配置SqlSessionFactoryBean,并设置其数据源为Atomikos的JTA数据源。这样,MyBatis的操作将在同一个全局事务内进行: ```xml <bean id="sqlSessionFactory" class="org.mybatis.spring....
本示例“springboot多数据源切换mysql+sql server”是关于如何在Spring Boot项目中配置和使用多个数据库的数据源切换。下面将详细阐述这个主题的知识点。 首先,**多数据源**是指在一个应用中同时连接并操作多个...
1. **配置数据源**:在Spring配置文件中,为每个数据库定义一个数据源,这些数据源可以通过`@Bean`注解创建。每个数据源对应一个特定的数据库连接池,如HikariCP或Druid。 2. **启用Atomikos**:引入Atomikos的依赖...
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。支持数据库敏感配置信息 加密(可自定义) ENC()。支持每个数据库独立初始化表结构schema和数据库database。支持无数据源启动,支持懒加载...
Spring Boot 整合 MyBatis 使用多数据源的实现方法 本文主要介绍了 Spring Boot 整合 MyBatis 使用多数据源的实现方法,旨在帮助读者了解如何在 Spring Boot 项目中使用多个数据源。下面将详细介绍多数据源的实现...
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...
一个基于springboot的快速集成多数据源的启动器简介dynamic-datasource-spring-boot-starter是一个基于springboot的快速集成多数据源的启动器。其支持Jdk 1.7 +,SpringBoot 1.4.x 1.5.x 2.xx。文件| 文献资料|特性...
4. **动态切换数据源**:在多库环境中,根据业务需求动态切换数据源是常见的需求。Spring框架提供了TransactionManager和AOP机制来支持这一功能。例如,通过@Qualifier注解指定数据源,或者使用...
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...
本文将深入探讨如何利用Spring Integration进行数据库数据的同步,并以`jdbc-inbound`为例进行详细解析。 首先,Spring Integration的核心理念是通过定义通道(Channel)和消息驱动的组件(Message-driven ...
下面将详细介绍如何在Spring Boot中整合多数据源并实现本地分布式事务。 一、多数据源设计 1. **配置多数据源**:Spring Boot允许我们配置多个数据源,通过不同的配置类或YAML/Properties文件来区分。每个数据源...
通过阅读并分析PageHelper的源代码,例如在GitHub上的pagehelper-fix分支,我们可以深入理解其内部实现机制,包括动态SQL的生成、数据库方言的处理、拦截器的使用等,这对于提升开发技能和优化项目性能都大有裨益。...
通过不同的配置文件(如`application-db1.properties`,`application-db2.properties`)可以设定不同的数据库连接信息,Spring Boot会自动识别并加载相应的数据源。 2. **JPA实体(Entities)**:这是ORM的核心,是...
介绍多库系统和数据库系统的概念、方法、理论
### 多库多事务降低数据不一致概率 #### 案例缘起 在实际的业务场景中,为了确保数据的正确性和完整性,通常会利用事务处理机制来保证数据库操作的ACID特性(原子性、一致性、隔离性、持久性)。以一个简单的订单...
例如,当jtaTx事务涉及到多库操作时,由于数据库连接没有根据不同的数据源切换,会抛出“Table 'merchant.orders' doesn't exist”的错误。为了解决这个情况,我们需要对JtaTransactionManager进行扩展,添加一个`...