最近搭建架构,碰到JTA和事务Transaction的问题,在此做个总结:
架构:Mybatis+ Spring
技术:spring的AbstractRoutingDataSource和JTA
老规矩,先贴代码,在讲原理,刚开始的时候不使用JTA,代码如下:
- /**
- * 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();
- }
- }
- /**
- * 动态数据源
- *
- */
- public class DynamicDataSource extends AbstractRoutingDataSource {
- @Override
- protected Object determineCurrentLookupKey() {
- return DataSourceContextHolder.getDataSourceType();
- }
- }
spring中配置如下:
- <!-- 配置数据源 -->
- <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
- <bean id="transactionManager"
- class="org.springframework.transaction.jta.JtaTransactionManager" />
- <tx:annotation-driven transaction-manager="transactionManager" />
当然,Datasource换成JNDI获取
- <!-- 创建数据源。 -->
- <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事务中,无法切换数据源,忙活了好久,对着日志流程和源代码,貌似问题出现在下面的代码中:
- 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);
- ransactionSynchronizationManager.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>
- <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测试代码如下:
- @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),此种情况用到动态切换,就显得方便很多。。。
相关推荐
本篇文档主要讲述如何使用Spring Boot结合MyBatis、JTA(Java Transaction API)以及Atomikos实现多数据源的事务管理。 ### Spring Boot框架 Spring Boot是Spring家族中的一个全新框架,用于简化新Spring应用的...
本示例项目"spring+mybatis+jta实现多数据源的分布式事务"提供了一种解决方案,利用Spring框架、MyBatis持久层框架以及Java Transaction API (JTA)来处理跨多个数据源的事务一致性。以下是对这一技术栈及其应用的...
本项目"java+spring+mybatis+mysql+RuoYi-atomikos-实现分布式事务.zip"是一个基于若依(RuoYi)框架改造的多模块分布式事务解决方案,它利用了Atomikos这一强大的分布式事务管理器。以下将详细解析这个项目的知识点...
本案例主要探讨如何利用Spring Boot、Atomikos、JTA(Java Transaction API)、Hibernate以及MyBatis,结合MySQL数据库,实现一个跨数据源的分布式事务解决方案。 首先,Spring Boot是一个基于Spring框架的快速开发...
Spring MVC、MyBatis 和 JTA(Java Transaction API)的集成是企业级应用中常见的技术组合,用于构建可扩展且支持多数据源的应用程序。在本项目中,我们看到一个配置了多数据源的环境,其中Spring MVC作为前端控制器...
本文将深入探讨如何使用Spring、MyBatis和Atomikos实现JTA分布式事务。 首先,让我们理解JTA的概念。Java Transaction API(JTA)是Java EE规范之一,用于处理分布式环境中的事务。它允许应用程序在不同的资源管理...
在本教程中,我们将深入探讨如何在Spring MVC 3中结合MyBatis并利用JTA(Java Transaction API)以及JBoss 7.1应用程序服务器来配置多数据源环境。这个配置对于那些需要处理多个数据库或者分布式事务的项目来说至关...
7. **源码分析**:在"Spring+JTA+Mybatis源码"中,你可以深入理解以下内容: - 如何在Spring配置文件中设置JTA事务管理器。 - MyBatis是如何与JTA配合进行事务控制的,比如在XML映射文件或Mapper接口中定义的事务...
本教程主要关注如何将Spring、SpringMVC、MyBatis和Atomikos这四个组件进行整合,以实现多数据源的分布式事务处理。 首先,Spring是Java领域广泛应用的依赖注入(DI)和面向切面编程(AOP)框架,它允许开发者轻松...
本资源"spring+mybatis+atomikos配置文件及相关包(最新版)"提供了整合Spring、MyBatis和Atomikos的配置文件和相关库,以支持在分布式环境下的事务管理。下面将详细解释这三个组件以及它们在分布式事务中的作用。 ...
NULL 博文链接:https://lizhao6210-126-com.iteye.com/blog/1958831
《Spring+Mybatis+Atomikos 配置与集成详解》 在分布式系统中,事务管理是至关重要的,尤其在微服务架构下。本篇文章将深入探讨如何在Spring框架中结合Mybatis和Atomikos进行分布式事务管理的配置与集成。Atomikos...
同时,你需要在MyBatis的配置中启用JTA事务管理,确保Spring和Atomikos能够协同工作。这样,当你的应用执行跨库操作时,Atomikos会自动管理这些操作,保证其在分布式环境下的原子性、一致性、隔离性和持久性。 综上...
本示例"spring3.0+struts2.0+mybatis3.2+jta+xapool配置文件"就是一个典型的Java企业级应用的集成框架,用于实现数据持久层、业务逻辑层以及控制层的整合。以下将详细介绍这些组件及其配置要点。 **Spring 3.0** 是...
在Spring Boot框架中,集成MyBatis和MySQL数据库并利用JTA(Java Transaction API)来管理多数据源的事务是一项常见的需求。这个压缩包文件名表明了它包含了一个示例项目,展示了如何在Spring Boot环境下配置和使用...
综上所述,Spring、MyBatis和Atomikos的集成主要涉及JTA标准的运用、Atomikos的配置以及Spring的事务管理。通过这种方式,可以实现在分布式环境下的事务一致性,确保多数据源操作的正确性。在实际开发中,根据具体的...
本教程将探讨如何利用Spring Boot、Druid、Mybatis以及Atomikos来配置多数据源并实现分布式事务。 首先,Spring Boot是Java生态系统中的一个流行框架,它简化了设置和配置过程,使得开发人员可以快速启动新项目。在...
<bean id="shardingDataSource" class="org.apache.shardingsphere.shardingjdbc.spring.boot.jta.datasource.SpringBootJtaShardingDataSource"> ``` 4. **配置MyBatis**:在`mybatis-config.xml`中,配置...
Spring Boot、MyBatis 和 Atomikos 的结合提供了一种解决方案,用于处理多数据源的分布式事务管理。以下是对这个主题的详细阐述。 首先,Spring Boot 是一个基于 Spring 框架的轻量级开发工具,它简化了配置过程并...
本DEMO展示了如何使用Spring框架、Mybatis持久层框架以及Atomikos事务管理器来实现这一目标。以下是对这个DEMO中涉及的技术点的详细说明。 首先,Spring框架作为Java领域中最受欢迎的IoC(Inversion of Control)和...