springboot-jta-atomikos多資料來源事務管理

帽子平頭發表於2022-03-12

背景

我們平時在用springboot開發時,要使用事務,只需要在方法上新增@Transaction註解即可,但這種方式只適用單資料來源,在多資料來源下就不再適用;

比如在多資料來源下,我們在一個方法裡執行了資料來源A的操作,又執行了資料來源B的操作,如果報錯了,事務只會回滾主資料來源或者是指定事務的資料來源資料(@Transactional(value="指定事務")),另一個資料來源是不會回滾的;

這種情況下,單純的@Transactional事務註解是無法實現的,此時就需要用到多資料來源事務管理;

以下專案裡實現了普通情況下的事務處理和使用springboot-jta-atomikos事務處理

本文主要介紹使用springboot-jta-atomikos來實現;

原始碼地址

https://github.com/lvlq73/springboot-jta-atomikos

專案目錄結構

 

 

 實現

1.新增依賴 pom.xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>


2.配置資料庫連線資訊 application.properties

#atomikos測試
spring.datasource.test1.url=jdbc:mysql://127.0.0.1:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
spring.datasource.test1.user=root
spring.datasource.test1.password=arsenal

spring.datasource.test2.url=jdbc:mysql://127.0.0.1:3306/test2?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
spring.datasource.test2.user=root
spring.datasource.test2.password=arsenal

3.建立多資料來源 DBAtomikosConfig.java

package com.llq.atomikos.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Properties;

/**
 * @author lvlianqi
 * @description
 * @date 2022/3/7
 */
@Configuration
public class DBAtomikosConfig {

    //--------------------資料來源1--------------------
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    @Bean
    public Properties testOneProperties() {
        return new Properties();
    }

    @Bean(name = "testOneDataSource")
    @Primary
    public DataSource testOneDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = testOneProperties();
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setUniqueResourceName("testOne");
        ds.setXaProperties(prop);
        return ds;
    }

    @Bean
    @Primary
    public JdbcTemplate testOneJdbcTemplate(@Qualifier("testOneDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    //--------------------資料來源2--------------------
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    @Bean
    public Properties testTwoProperties() {
        return new Properties();
    }

    @Bean(name = "testTwoDataSource")
    public DataSource testTwoDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = testTwoProperties();
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setUniqueResourceName("testTwo");
        ds.setXaProperties(prop);
        return ds;
    }

    @Bean
    public JdbcTemplate testTwoJdbcTemplate(@Qualifier("testTwoDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    //--------------------配置spring的JtaTransactionManager,底層委派給atomikos進行處理--------------------
    @Bean
    public JtaTransactionManager jtaTransactionManager () {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }
}

4.測試事務類 TestAtomikos.java

package com.llq.atomikos.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author lvlianqi
 * @description
 * @date 2022/3/7
 */
@Service
public class TestAtomikos implements ITest{

    @Qualifier("testOneJdbcTemplate")
    @Autowired
    private JdbcTemplate testOneJdbcTemplate;

    @Qualifier("testTwoJdbcTemplate")
    @Autowired
    private JdbcTemplate testTwoJdbcTemplate;

    /**
     * 測試正常情況
     */
    @Transactional(rollbackFor = Exception.class, value = "jtaTransactionManager")
    public void test() {
        testOneJdbcTemplate.execute("insert into user (name, age) values ('張三', 18);");
        testTwoJdbcTemplate.execute("insert into user (name, age) values ('李四', 20);");
    }

    /**
     * 測試異常情況
     */
    @Transactional(rollbackFor = Exception.class, value = "jtaTransactionManager")
    public void testByException() {
        testOneJdbcTemplate.execute("insert into user (name, age) values ('張三', 18);");
        testTwoJdbcTemplate.execute("insert into user (name, age) values ('李四', 20);");
        int i = 1/0;
    }
}

5.測試 SpringbootAtomikosApplicationTests.java

    //使用atomikos
    private static Class CLS = TestAtomikos.class;

    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void testByException() {
        ITest test = (ITest) applicationContext.getBean(CLS);
        test.testByException();
    }

測試結果

執行錯誤
image
資料庫test1 user表沒有記錄
image
資料庫test2 user表沒有記錄
image

相關文章