spring多数据源分布式事务管理

一、背景

项目中使用到了两个数据源,且在同一个方法里用到了这两个数据源,并且需要保证事务一致性。

二、解决方案

解决该问题需要使用分布式事务处理,常见的实现JTA事务管理的第三方管理工具 ,一个是JOTM,一个是Atomikos。JOTM于2009年3月份后已不再更新,而且spring3以后不在支持jotm。

系统框架基于Spring5+Mybatis3+mysql,所以这里使用atomikos+jta的方案来处理该问题。

JTA(ava Transaction API): JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JDBC驱动程序的JTA支持极大地增强了数据访问能力。
Atomikos(Atomikos TransactionsEssentials): 是一个为Java平台提供的开源事务管理器,全名Atomikos TransactionsEssentials.
JOTM(Java Open Transaction Manager): 是ObjectWeb的一个开源JTA实现,它本身也是开源应用程序服务器JOnAS(Java Open Application Server)的一部分,为其提供JTA分布式事务的功能。

三、代码实现

代码结构

在这里插入图片描述

Dependency
<!-- atomikos -->
<dependency>
	<groupId>com.atomikos</groupId>
	<artifactId>transactions-jdbc</artifactId>
	<version>3.9.3</version>
</dependency>
        
<!-- jta -->
<dependency>
	<groupId>javax.transaction</groupId>
	<artifactId>jta</artifactId>
	<version>1.1</version>
</dependency>
配置文件

这里为了演示不同的数据源实现方案,同时使用了两种类型的数据源,读者可根据项目情况使用。
datasourcea.xml: 数据源1采用DruidXADataSource

<!-- druid xa datasource -->
    <bean id="aDruidXA" class="com.alibaba.druid.pool.xa.DruidXADataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url">
            <value>
                jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&amp;characterEncoding=utf-8
            </value>
        </property>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="filters" value="stat"/>
        <property name="maxActive" value="100"/>
        <property name="initialSize" value="10"/>
        <property name="maxWait" value="60000"/>
        <property name="minIdle" value="10"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="50"/>
        <property name="maxOpenPreparedStatements" value="100"/>
    </bean>

    <!-- datasource1数据源 -->
    <bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean"
          init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="dataSource1"/>
        <property name="xaDataSource" ref="aDruidXA"/>
        <!-- NOTE: 注意不要添加以下注释的参数 -->
        <!--<property name="poolSize" value="10"/>-->
        <!--<property name="minPoolSize" value="10"/>-->
        <property name="maxPoolSize" value="100"/>
        <property name="borrowConnectionTimeout" value="30"/>
        <property name="testQuery" value="select 1"/>
        <property name="maintenanceInterval" value="60"/>
    </bean>

    <!-- sessionFactory 将spring和mybatis整合 -->
    <bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource1"/>
        <property name="mapperLocations">
            <array>
                <value>classpath*:com/study/mappera/**/*.xml</value>
            </array>
        </property>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.study.daoa"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory1"/>
    </bean>

datasourceb.xml: 数据源2使用MysqlXADataSource

 <!-- datasource2数据源 -->
    <bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean"
          init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="dataSource2"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <!-- NOTE: 对于Druid的XA数据源的另一种配置方法 -->
        <!-- <property name="xaDataSourceClassName" value="com.alibaba.druid.pool.xa.DruidXADataSource"/> -->
        <property name="xaProperties">
            <props>
                <prop key="url">
                    jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&amp;characterEncoding=utf-8
                </prop>
                <prop key="user">root</prop>
                <prop key="password">root</prop>
            </props>
        </property>
        <property name="poolSize" value="10"/>
        <property name="minPoolSize" value="10"/>
        <property name="maxPoolSize" value="100"/>
        <property name="borrowConnectionTimeout" value="30"/>
        <property name="testQuery" value="select 1"/>
        <property name="maintenanceInterval" value="60"/>
    </bean>

    <bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource2"/>
        <property name="mapperLocations">
            <array>
                <value>classpath*:com/study/mapperb/**/*.xml</value>
            </array>
        </property>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.study.daob"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory2"/>
    </bean>

spring-context.xml

    <!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 -->
    <context:component-scan base-package="com.study.service"/>

    <!-- 引入数据A -->
   <import resource="datasourcea.xml"/>

    <!-- 引入数据源B driud数据源-->
   <import resource="datasourceb.xml"/>


    <!-- atomitos事务管理器 -->
    <bean id="atomitosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
          init-method="init" destroy-method="close">
        <property name="forceShutdown" value="true"/>
    </bean>

    <!-- atomitos事务 -->
    <bean id="atomikosTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="600"/>
    </bean>

    <!-- Spring JTA事务管理器 -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomitosTransactionManager"/>
        <property name="userTransaction" ref="atomikosTransaction"/>
        <property name="allowCustomIsolationLevels" value="true"/>
        <property name="globalRollbackOnParticipationFailure" value="true"/>
    </bean>

    <!-- 注解事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 使用annotation定义事务 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="query*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="select*" propagation="REQUIRED" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(public * com.study.service..*.*(..))" id="tranPointcut"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="tranPointcut" order="2"/>
    </aop:config>
代码

ServeiceTest.java测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-context.xml"})
public class ServiceTest {

    @Autowired
    private DTService dtService;

    /**
     * 单个数据源事务提交
     */
    @Test
    public void test_01() {
        dtService.saveDataSource1_A();
    }

    /**
     * 多个数据源提交 事务成功
     */
    @Test
    public void test_02() {
        dtService.saveTwoDataSourceSuccess();
    }

    /**
     * 多个数据源提交 事务回滚
     */
    @Test
    public void test_03() {
        dtService.saveTwoDataSourceFail();
    }
}

完整代码:https://github.com/zqhao/spring-mybatis-atomikos


------------本文结束感谢您的阅读------------
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值