• spring整合atomikos实现分布式事务


    前言

    Atomikos 是一个为Java平台提供增值服务的并且开源类事务管理器,主要用于处理跨数据库事务,比如某个指令在A库和B库都有写操作,业务上要求A库和B库的写操作要具有原子性,这时候就可以用到atomikos。笔者这里整合了一个spring和atomikos的demo,并且通过案例演示说明atomikos的作用。

    准备工作

    开发工具:idea

    数据库:mysql , oracle

    正文

    源码地址:github.com/qw870602/at…

    演示原理:通过在两个库的写操作之间人为制造异常来观察数据库是否回滚

    演示步骤 :1.正常写操作,观察数据库值的变化情况

           2.在写操作语句之间制造异常,观察数据库值的变化情况

    项目结构

     
     

    从web.xml中可以知道,容器只加载了appliactionContext.xml,剩下的配置文件除了database.properties外都是无用文件,所以大家如果要在项目中配置的话,仅需要把appliactionContext.xml中关于atomikos的部分新增到自己项目中就OK了

    appliactionContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
            xmlns="http://www.springframework.org/schema/beans"
            xmlns:tx="http://www.springframework.org/schema/tx"
            xmlns:p="http://www.springframework.org/schema/p"
            xmlns:aop="http://www.springframework.org/schema/aop"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:mvc="http://www.springframework.org/schema/mvc"
            xmlns:context="http://www.springframework.org/schema/context"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
        <!-- 引入数据源信息的properties属性文件 -->
        <context:property-placeholder location="classpath:database.properties" />
        <!-- XA方式 -->
        <!-- MYSQL数据库配置 -->
        <bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
            <property name="uniqueResourceName" value="dataSource1"/>
            <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
            <property name="xaProperties">
                <props>
                    <prop key="URL">${mysql.qa.db.url}</prop>
                    <prop key="user">${mysql.qa.db.user}</prop>
                    <prop key="password">${mysql.qa.db.password}</prop>
                </props>
            </property>
            <property name="minPoolSize" value="10" />
            <property name="maxPoolSize" value="100" />
            <property name="borrowConnectionTimeout" value="30" />
            <property name="maintenanceInterval" value="60" />
        </bean>
    
        <!-- ORACLE数据库配置 -->
        <bean id="oracleDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
            <property name="uniqueResourceName" value="dataSource2"/>
            <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource" />
            <property name="xaProperties">
                <props>
                    <prop key="URL">${oracle.qa.db.url}</prop>
                    <prop key="user">${oracle.qa.db.user}</prop>
                    <prop key="password">${oracle.qa.db.password}</prop>
                </props>
            </property>
            <property name="minPoolSize" value="10" />
            <property name="maxPoolSize" value="100" />
            <property name="borrowConnectionTimeout" value="30" />
            <property name="maintenanceInterval" value="60" />
        </bean>
    
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--<property name="configLocation" value="classpath:mybatis-config-mysql.xml" />-->
            <property name="dataSource" ref="mysqlDataSource" />
            <property name="mapperLocations" >
                <list>
                    <value>classpath*:/dao/*.xml</value>
                </list>
            </property>
        </bean>
        <bean id="sqlSessionFactoryOracle" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--<property name="configLocation" value="classpath:mybatis-config.xml" />-->
            <property name="dataSource" ref="oracleDataSource" />
            <property name="mapperLocations" >
                <list>
                    <value>classpath*:/daodev/*.xml</value>
                </list>
            </property>
        </bean>
    
        <!-- MyBatis为不同的mapper注入sqlSessionFactory -->
        <bean id="mysqlTransactionTestDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
            <property name="sqlSessionFactory" ref="sqlSessionFactory" />
            <property name="mapperInterface" value="com.xy.dao.MysqlTransactionTestDao" />
        </bean>
        <bean id="transactionTestDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
            <property name="sqlSessionFactory" ref="sqlSessionFactoryOracle" />
            <property name="mapperInterface" value="com.xy.dao.TransactionTestDao" />
        </bean>
    
        <!-- 分布式事务 -->
        <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
            <property name="forceShutdown" value="true"/>
        </bean>
        <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
            <property name="transactionTimeout" value="300"/>
        </bean>
        <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
            <property name="transactionManager" ref="atomikosTransactionManager"/>
            <property name="userTransaction" ref="atomikosUserTransaction"/>
        </bean>
    
        <tx:annotation-driven transaction-manager="transactionManager"/>
        <context:annotation-config/>
        <!--&lt;!&ndash; 自动扫描controller包下的所有类,如果@Controller注入为bean &ndash;&gt;-->
        <!--&lt;!&ndash;事务管理层&ndash;&gt;-->
        <context:component-scan base-package="com.xy" />
    
        <!-- 注册拦截器 -->
        <!--<mvc:interceptors>
            <bean class="com.springmybatis.system.interceptor.MyInterceptor" />
        </mvc:interceptors>-->
    </beans>复制代码
    

    适用JUnit4进行单元测试

    package com.xy.controller;
    
    import com.xy.daodev.TransactionTestService;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    
    @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
    public class TransactionTestMain extends AbstractJUnit4SpringContextTests {
       @Autowired
     private TransactionTestService transactionTestService;
    
     /**
      * 在同一事务有多个数据源
      */
     @Test
     public void multipleDataSource2() {
      transactionTestService.updateMultipleDataSource("1","1", 100L,"1.6");
     }
    }

    业务实现,当前没有异常操作

    @Service
    public class TransactionTestServiceImpl implements TransactionTestService {
       @Autowired
     @Qualifier("mysqlTransactionTestDao")
     private MysqlTransactionTestDao mysqlTransactionTestDao;
    
     @Autowired
     @Qualifier("transactionTestDao")
     private TransactionTestDao transactionTestDao;
    
     /**
      * 在同一事务有多个数据源
      */
     @Override
     @Transactional
     public void updateMultipleDataSource(String deUserId, String inUserid, long money,String str) {
      // 账户1转出操作
      mysqlTransactionTestDao.decreaseMoney(deUserId, money);
        //Integer.parseInt(str);
      // 账户2转入操作
      transactionTestDao.increaseMoney(inUserid, money);
    
     }  
    
    }

    mysql模拟金额转出,oracle模拟金额转入

    <update id="decreaseMoney" parameterType="java.util.Map">
        UPDATE fx1 SET amount=amount - #{1,jdbcType=BIGINT} WHERE id=#{0,jdbcType=VARCHAR}
    </update><update id="increaseMoney">
        UPDATE fx1 SET amount=amount + #{1,jdbcType=BIGINT} WHERE id=#{0,jdbcType=VARCHAR}
    </update>

    mysql初始金额

     
     

    oracle初始金额

     
     

    执行正常操作

     
     

    mysql当前金额

     
     

    oracle当前金额

     
     

    将被屏蔽的制造异常的代码打开

    public void updateMultipleDataSource(String deUserId, String inUserid, long money,String str) {
     // 账户1转出操作
     mysqlTransactionTestDao.decreaseMoney(deUserId, money);
       Integer.parseInt("skg");
     // 账户2转入操作
     transactionTestDao.increaseMoney(inUserid, money);
    } 

    发现mysql和oracle的当前金额都没有变化,说明事务回滚成功,查看日志

     
     

    发现控制台打印出了异常信息,并且atomikos调用了rollback()方法,从日志也证实了回滚成功。

    END

    作者:胖子k
    链接:https://juejin.im/post/5ce24d4ce51d45109b01b0f4

  • 相关阅读:
    20200924-4 代码规范,结对要求
    20200924-2 功能测试
    20200917-1 每周例行报告
    20200917-3 白名单
    20200917-2 词频统计 已更新附加题!
    20200910-1 每周例行报告
    20200924-2功能测试
    20200924-1每周例行报告
    20200924-3单元测试
    20200924-5 四则运算,结对
  • 原文地址:https://www.cnblogs.com/pypua/p/11498717.html
Copyright © 2020-2023  润新知