在探事務(wù)-JTA處理分布式事務(wù)

事務(wù)在后端中是占著很重要的地位,也是一個比較難處理的地方。以我現(xiàn)在所知可以將事務(wù)分為兩大類,一類本地事務(wù),一類分布式事務(wù)。分類標(biāo)準(zhǔn)也很簡單,本地事務(wù)就是一個系統(tǒng)只用一個數(shù)據(jù)庫,且只在一個項目下。分布式事務(wù)就是一個系統(tǒng)用多個數(shù)據(jù)庫,或一個項目用了多個數(shù)據(jù)庫。本篇呢就簡單記錄下在一個項目下用多個數(shù)據(jù)庫的事務(wù)處理。

一、初識JTA

JTA,即Java Transaction API,JTA允許應(yīng)用程序執(zhí)行分布式事務(wù)處理——在兩個或多個網(wǎng)絡(luò)計算機資源上訪問并且更新數(shù)據(jù)。JDBC驅(qū)動程序的JTA支持極大地增強了數(shù)據(jù)訪問能力。

這是來自百度百科的解釋。他的根本目標(biāo)就是為了多數(shù)據(jù)庫下的事務(wù)統(tǒng)一,維護ACID特性。

要使用JTA事務(wù),必須使用XADataSource來產(chǎn)生數(shù)據(jù)庫連接,產(chǎn)生的連接為一個XA連接。

這是使用JTA事務(wù)的前提,數(shù)據(jù)源必須是XADataSource。在這個條件的約束下一些我們常用的數(shù)據(jù)庫連接池(如:hikaricp,c3p0,dbcp)就沒法使用咯。但我們熟知的阿里還是很強的,阿里的druid提供了對XADataSource的支持。

JTA只是一套接口定義,具體實現(xiàn)要靠各個廠商的支持。本次使用的是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

<!--全局事務(wù) 分布式事務(wù)1 多數(shù)據(jù)源-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

其實需要很多的依賴,這里transaction-jdbc會自動依賴其他需要的。

三、進行JTA配置

之前也說過了要支持JAT事務(wù),一般的數(shù)據(jù)源是不能使用的,所以一般的數(shù)據(jù)庫連接池就不能使用了。本次我也沒有使用阿里家的數(shù)據(jù)庫連接池,直接使用Atimikos提供的數(shù)據(jù)庫連接池。

1、SSM項目配置

  • 多數(shù)據(jù)源配置
  <!-- 一號數(shù)據(jù)源配置 -->
  <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"/> <!-- 必填項,且每個數(shù)據(jù)源要不一樣 -->
  </bean>

  <!-- 二號數(shù)據(jù)源配置 -->
  <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>
  • 全局事務(wù)配置
  <!-- jta全局事務(wù)配置 -->
  <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工廠具體配置如下
  <!--一號數(shù)據(jù)源下的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>

  <!--二號數(shù)據(jù)源下的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配置

  • 多數(shù)據(jù)源以及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 {

//  ----------- 主數(shù)據(jù)源 -----------
    @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;
    }

//  ------------- 第二數(shù)據(jù)源 -------------
    @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;
    }

//  ---------- 不同數(shù)據(jù)源對應(yīng)的 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事務(wù)配置
@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事務(wù)管理的配置。

四、測試

完成已上的各種配置后,我們就可以像使用本地事務(wù)那樣實現(xiàn)全局事務(wù)的處理了。這里就是只貼出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;
        });
    }
    ......
}

當(dāng)num為0時,產(chǎn)生異常事務(wù)回滾。

五、總結(jié)

搗鼓了好久才調(diào)通,在這里記錄下。出現(xiàn)的問題有1、再配置全局事務(wù)的同時,還配置了jdbc的事務(wù),這是無法使用的。2、在Controller中是用事務(wù)。不好意思也沒成功,主要是ssm中配置的時候事務(wù)只對非controller層中有效

源碼地址JTA樣例

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容