`
jijun87120681
  • 浏览: 41294 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

多库数据源深入分析(Mybatis+ Spring + JTA)(二)

    博客分类:
  • java
阅读更多

接上篇,为什么此种模式下,在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),此种情况用到动态切换,就显得方便很多。。。

 

 

 

 

 

 

 

分享到:
评论
4 楼 AKka 2014-07-10  
JTA需要一个UserTransaction,请问楼主怎么配置?
3 楼 AKka 2014-07-04  
testxa,testxa1这两个的配置贴一下呗,楼主!
2 楼 yjjie 2013-04-23  
楼主这个方式不灵活!
如果我有N个库呢?(库结构相同)
1 楼 shut1k 2013-03-14  
跟着楼主的方式走启动的时候抛异常啊,
No unique bean of type [org.apache.ibatis.session.SqlSessionFactory] is defined

相关推荐

    springboot + mybatis-plus + oracle + 多数据源 + redis + hutool

    springboot + mybatis-plus + database+ 多数据源 + redis + hutool 框架干净,没有其他冗余的成分; 配置了MP的代码生成器,意见生成代码,节省开发时间! 可用于各种定时任务处理,各种夸库操作, 多数据源支持...

    spring3+hibernate4+maven+junit 多库多数据源实现

    "Spring3+Hibernate4+Maven+JUnit 多库多数据源实现"是一个典型的Java Web项目配置,它涉及了多个核心技术来处理复杂的数据管理需求。下面将详细阐述这些技术以及如何协同工作以实现多库多数据源。 首先,Spring...

    myBatis多数据源配置

    myBatis 多数据源配置就是为了解决这样的问题。myBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。与 Spring Boot 结合使用时,可以方便地管理和切换不同的数据源。 首先,我们要理解什么...

    spring mybatis atomikos 多库分布式事务demo

    然后,对于MyBatis,我们需要配置SqlSessionFactoryBean,并设置其数据源为Atomikos的JTA数据源。这样,MyBatis的操作将在同一个全局事务内进行: ```xml &lt;bean id="sqlSessionFactory" class="org.mybatis.spring....

    springboot多数据源切换mysql+sql server事例

    本示例“springboot多数据源切换mysql+sql server”是关于如何在Spring Boot项目中配置和使用多个数据库的数据源切换。下面将详细阐述这个主题的知识点。 首先,**多数据源**是指在一个应用中同时连接并操作多个...

    纯spring多库

    1. **配置数据源**:在Spring配置文件中,为每个数据库定义一个数据源,这些数据源可以通过`@Bean`注解创建。每个数据源对应一个特定的数据库连接池,如HikariCP或Druid。 2. **启用Atomikos**:引入Atomikos的依赖...

    基于 SpringBoot 多数据源 动态数据源 主从分离 快速启动器 支持分布式事务

    支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。支持数据库敏感配置信息 加密(可自定义) ENC()。支持每个数据库独立初始化表结构schema和数据库database。支持无数据源启动,支持懒加载...

    Spring Boot 整合mybatis 使用多数据源的实现方法

    Spring Boot 整合 MyBatis 使用多数据源的实现方法 本文主要介绍了 Spring Boot 整合 MyBatis 使用多数据源的实现方法,旨在帮助读者了解如何在 Spring Boot 项目中使用多个数据源。下面将详细介绍多数据源的实现...

    基于 SpringBoot的多数据源 动态数据源 主从分离 快速启动器 支持分布式事务

    支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...

    dynamic-datasource-spring-boot-starter:springboot的动态数据源多数据源动态数据源主从分离读写分离分布式事务https:dynamic-datasource.com

    一个基于springboot的快速集成多数据源的启动器简介dynamic-datasource-spring-boot-starter是一个基于springboot的快速集成多数据源的启动器。其支持Jdk 1.7 +,SpringBoot 1.4.x 1.5.x 2.xx。文件| 文献资料|特性...

    分库代码(备份库、建库,动态创建数据源、动态切换数据源)

    4. **动态切换数据源**:在多库环境中,根据业务需求动态切换数据源是常见的需求。Spring框架提供了TransactionManager和AOP机制来支持这一功能。例如,通过@Qualifier注解指定数据源,或者使用...

    dynamic-datasource-spring-boot-starter-v3.5.1.zip

    支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...

    dynamic-datasource-spring-boot-starter-v3.5.1.tar.gz

    支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 支持数据库敏感配置信息 加密(可自定义) ENC()。 支持每个数据库独立初始化表结构schema和数据库database。 支持无数据源启动,支持懒加载...

    spring integration同步数据库数据

    本文将深入探讨如何利用Spring Integration进行数据库数据的同步,并以`jdbc-inbound`为例进行详细解析。 首先,Spring Integration的核心理念是通过定义通道(Channel)和消息驱动的组件(Message-driven ...

    SpringBoot整合多数据源,并实现本地分布式事务

    下面将详细介绍如何在Spring Boot中整合多数据源并实现本地分布式事务。 一、多数据源设计 1. **配置多数据源**:Spring Boot允许我们配置多个数据源,通过不同的配置类或YAML/Properties文件来区分。每个数据源...

    Mybatis分页插件 - PageHelper.zip

    通过阅读并分析PageHelper的源代码,例如在GitHub上的pagehelper-fix分支,我们可以深入理解其内部实现机制,包括动态SQL的生成、数据库方言的处理、拦截器的使用等,这对于提升开发技能和优化项目性能都大有裨益。...

    spring boot 常用数据库操作例子

    通过不同的配置文件(如`application-db1.properties`,`application-db2.properties`)可以设定不同的数据库连接信息,Spring Boot会自动识别并加载相应的数据源。 2. **JPA实体(Entities)**:这是ORM的核心,是...

    多库系统与数据集成技术

    介绍多库系统和数据库系统的概念、方法、理论

    多库多事务降低数据不一致概率

    ### 多库多事务降低数据不一致概率 #### 案例缘起 在实际的业务场景中,为了确保数据的正确性和完整性,通常会利用事务处理机制来保证数据库操作的ACID特性(原子性、一致性、隔离性、持久性)。以一个简单的订单...

    Atomikos jta事务框架改写历程

    例如,当jtaTx事务涉及到多库操作时,由于数据库连接没有根据不同的数据源切换,会抛出“Table 'merchant.orders' doesn't exist”的错误。为了解决这个情况,我们需要对JtaTransactionManager进行扩展,添加一个`...

Global site tag (gtag.js) - Google Analytics