美文网首页javaWeb学习分布式相关技术
在探事务-JTA处理分布式事务

在探事务-JTA处理分布式事务

作者: 咻咻咻i | 来源:发表于2019-03-16 16:28 被阅读56次

    事务在后端中是占着很重要的地位,也是一个比较难处理的地方。以我现在所知可以将事务分为两大类,一类本地事务,一类分布式事务。分类标准也很简单,本地事务就是一个系统只用一个数据库,且只在一个项目下。分布式事务就是一个系统用多个数据库,或一个项目用了多个数据库。本篇呢就简单记录下在一个项目下用多个数据库的事务处理。

    一、初识JTA

    JTA,即Java Transaction API,JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JDBC驱动程序的JTA支持极大地增强了数据访问能力。

    这是来自百度百科的解释。他的根本目标就是为了多数据库下的事务统一,维护ACID特性。

    要使用JTA事务,必须使用XADataSource来产生数据库连接,产生的连接为一个XA连接。

    这是使用JTA事务的前提,数据源必须是XADataSource。在这个条件的约束下一些我们常用的数据库连接池(如:hikaricp,c3p0,dbcp)就没法使用咯。但我们熟知的阿里还是很强的,阿里的druid提供了对XADataSource的支持。

    JTA只是一套接口定义,具体实现要靠各个厂商的支持。本次使用的是Atomikos

    二、在spring中添加JAT依赖

    使用maven,添加pom依赖

    1、SSM

    <!--jta支持-->
    <dependency>
      <groupId>com.atomikos</groupId>
      <artifactId>transactions-jdbc</artifactId>
      <version>4.0.6</version>
    </dependency>
    
    <dependency>
      <groupId>javax.transaction</groupId>
      <artifactId>javax.transaction-api</artifactId>
      <version>1.3</version>
    </dependency>
    

    2、springboot

    <!--全局事务 分布式事务1 多数据源-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    </dependency>
    

    其实需要很多的依赖,这里transaction-jdbc会自动依赖其他需要的。

    三、进行JTA配置

    之前也说过了要支持JAT事务,一般的数据源是不能使用的,所以一般的数据库连接池就不能使用了。本次我也没有使用阿里家的数据库连接池,直接使用Atimikos提供的数据库连接池。

    1、SSM项目配置

    • 多数据源配置
      <!-- 一号数据源配置 -->
      <bean id="ds01" class="com.atomikos.jdbc.AtomikosDataSourceBean">
        <property name="xaDataSource">
          <bean class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
            <property name="url" value="jdbc:mysql:///multi_ds01"/>
            <property name="user" value="root"/>
            <property name="password" value="root"/>
          </bean>
        </property>
        <property name="poolSize" value="2"/>
        <property name="maxPoolSize" value="10"/>
        <property name="uniqueResourceName" value="ds01xa"/> <!-- 必填项,且每个数据源要不一样 -->
      </bean>
    
      <!-- 二号数据源配置 -->
      <bean id="ds02" class="com.atomikos.jdbc.AtomikosDataSourceBean">
        <property name="xaDataSource">
          <bean class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
            <property name="url" value="jdbc:mysql:///multi_ds02"/>
            <property name="user" value="root"/>
            <property name="password" value="root"/>
          </bean>
        </property>
        <property name="poolSize" value="2"/>
        <property name="maxPoolSize" value="10"/>
        <property name="uniqueResourceName" value="ds02xa"/>
      </bean>
    
    • 全局事务配置
      <!-- jta全局事务配置 -->
      <bean id="userTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="30000"/>
      </bean>
    
      <bean id="userTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
            depends-on="userTransaction" init-method="init" destroy-method="close">
        <property name="forceShutdown" value="false"/> <!-- 这里我也不知道干嘛,看很多教程都有 -->
      </bean>
    
      <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="userTransaction" ref="userTransaction"/>
        <property name="transactionManager" ref="userTransactionManager"/>
      </bean>
      
      <tx:annotation-driven transaction-manager="jtaTransactionManager"/>
    
    • 使用mybatisplus,sqlsession工厂具体配置如下
      <!--一号数据源下的sql session工厂-->
      <bean id="ssf01" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="ds01"/>
        <property name="typeAliasesPackage" value="cn.lkangle.entity"/>
      </bean>
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.lkangle.mapper.ds01"/>
        <property name="sqlSessionFactoryBeanName" value="ssf01"/>
      </bean>
    
      <!--二号数据源下的sql session-->
      <bean id="ssf02" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="ds02"/>
        <property name="typeAliasesPackage" value="cn.lkangle.entity"/>
      </bean>
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.lkangle.mapper.ds02"/>
        <property name="sqlSessionFactoryBeanName" value="ssf02"/>
      </bean>
    

    2、springboot配置

    • 多数据源以及sqlsession工厂配置
    @Configuration
    @MapperScan(basePackages = "cn.lkangle.multids01.mappers.mapper01",
            sqlSessionFactoryRef = "ds01_sqlSession")
    @MapperScan(basePackages = "cn.lkangle.multids01.mappers.mapper02",
            sqlSessionFactoryRef = "ds02_sqlSession")
    public class MultiDSPlusConfig {
    
    //  ----------- 主数据源 -----------
        @Bean("ds01")
        @Primary
        public DataSource data1Source() {
            MysqlXADataSource dataSource = new MysqlXADataSource();
            dataSource.setURL("jdbc:mysql:///multi_ds01");
            dataSource.setUser("root");
            dataSource.setPassword("root");
            dataSource.setPinGlobalTxToPhysicalConnection(true);
            AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
            dataSourceBean.setXaDataSource(dataSource);
            dataSourceBean.setMaxPoolSize(10);
            dataSourceBean.setUniqueResourceName("ds01_datasource");
            dataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.Driver");
            return dataSourceBean;
        }
    
    //  ------------- 第二数据源 -------------
        @Bean("ds02")
        public DataSource data2Source() {
            MysqlXADataSource dataSource = new MysqlXADataSource();
            dataSource.setURL("jdbc:mysql:///multi_ds02");
            dataSource.setUser("root");
            dataSource.setPassword("root");
            dataSource.setPinGlobalTxToPhysicalConnection(true);
            AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
            dataSourceBean.setXaDataSource(dataSource);
            dataSourceBean.setMaxPoolSize(10);
            dataSourceBean.setUniqueResourceName("ds02_datasource");
            dataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.Driver");
            return dataSourceBean;
        }
    
    //  ---------- 不同数据源对应的 sql session 工厂 -----------
        @Bean("ds01_sqlSession")
        public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds01") DataSource dataSource) throws IOException {
            MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
            Resource[] resources = loader.getResources("classpath:mapper/ds01/*.xml");
            bean.setMapperLocations(resources);
            return bean;
        }
    
        @Bean("ds02_sqlSession")
        public MybatisSqlSessionFactoryBean sqlSession2FactoryBean(@Qualifier("ds02") DataSource dataSource) throws IOException {
            MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
            Resource[] resources = loader.getResources("classpath:mapper/ds02/*.xml");
            bean.setMapperLocations(resources);
            return bean;
        }
    }
    
    • 开启全局JTA事务配置
    @Configuration
    @EnableTransactionManagement
    public class GlobalTxConfig {
    
        @Bean
        public UserTransaction userTransaction() throws SystemException {
            UserTransactionImp transactionImp = new UserTransactionImp();
            transactionImp.setTransactionTimeout(20000);
            return transactionImp;
        }
    
        @Bean(initMethod = "init", destroyMethod = "close")
        public UserTransactionManager userTransactionManager() {
            return new UserTransactionManager();
        }
    
        @Bean
        public JtaTransactionManager transactionManager(@Qualifier("userTransaction") UserTransaction userTransaction,
                                                        @Qualifier("userTransactionManager") UserTransactionManager userTransactionManager) {
            return new JtaTransactionManager(userTransaction, userTransactionManager);
        }
    }
    

    已上即完成了JTA事务管理的配置。

    四、测试

    完成已上的各种配置后,我们就可以像使用本地事务那样实现全局事务的处理了。这里就是只贴出service层的代码了。

    @Service
    public class UserService {
    
        @Autowired
        private TransactionTemplate transactionTemplate;
    
        @Autowired
        private Ds01Mapper ds01Mapper;
    
        @Autowired
        private Ds02Mapper ds02Mapper;
        
        // 声明式
        @Transactional
        public int insert1(MUser user) {
            int ds1 = ds01Mapper.insert(user);
            int ds2 = ds02Mapper.insert(user);
    
            int p = 9 / user.getNum();
    
            return ds1 + ds2;
        }
    
        // 编程式
        public int insert(MUser user) {
            return transactionTemplate.execute(status -> {
    
                System.out.println(status);
    
                int ds1 = ds01Mapper.insert(user);
                int ds2 = ds02Mapper.insert(user);
    
                int p = 9 / user.getNum();
    
                return ds1 + ds2;
            });
        }
        ......
    }
    

    当num为0时,产生异常事务回滚。

    五、总结

    捣鼓了好久才调通,在这里记录下。出现的问题有1、再配置全局事务的同时,还配置了jdbc的事务,这是无法使用的。2、在Controller中是用事务。不好意思也没成功,主要是ssm中配置的时候事务只对非controller层中有效

    源码地址JTA样例

    相关文章

      网友评论

        本文标题:在探事务-JTA处理分布式事务

        本文链接:https://www.haomeiwen.com/subject/pbysmqtx.html