首頁 > 軟體

SpringBoot2使用JTA元件實現基於JdbcTemplate多資料來源事務管理(親測好用)

2022-07-28 18:02:08

一、JTA元件簡介

什麼是JTA

JTA,全稱:Java Transaction API。JTA事務比JDBC事務更強大。一個JTA事務可以有多個參與者,而一個JDBC事務則被限定在一個單一的資料庫連線。所以,當我們在同時操作多個資料庫的時候,使用JTA事務就可以彌補JDBC事務的不足。

在Spring Boot 2.x中,整合了這兩個JTA的實現:

Atomikos:可以通過引入spring-boot-starter-jta-atomikos依賴來使用
Bitronix:可以通過引入spring-boot-starter-jta-bitronix依賴來使用

由於Bitronix自Spring Boot 2.3.0開始不推薦使用,所以在下面的動手環節中,我們將使用Atomikos作為例子來介紹JTA的使用。

什麼是XA協定

XA協定是資料庫層面的一套分散式事務管理的規範,JTA是XA協定在Java中的實現,多個資料庫或是訊息廠商實現JTA介面,開發人員只需要呼叫SpringJTA介面即可實現JTA事務管理功能。

二、SpringBoot整合JTA

準備工作

這裡我們將使用最基礎的JdbcTemplate來實現資料存取,所以如果你還不會使用JdbcTemplate設定多資料來源,建議先看一JdbcTemplate的多資料來源設定。

場景設定:

假設我們有兩個庫,分別為:test1和test2
這兩個庫中都有一張User表,我們希望這兩張表中的資料是一致的

假設這兩張表中都已經有一條資料:name=aaa,age=30;因為這兩張表中資料是一致的,所以要update的時候,就必須兩個庫中的User表更新時候,要麼都成功,要麼都失敗。

1、核心依賴

<!--JTA元件核心依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

2、環境設定

yml組態檔這裡jtaManager的設定,在紀錄檔輸出中非常關鍵。

spring:
  jta:
    enabled: true
    transaction-manager-id: jtaManager
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.jdbc.Driver
  backdatasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.jdbc.Driver

3、jta元件設定類

package com.sgcc.qfjs.config;

import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
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.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;


@Configuration
public class JtaDataSourceConfig {

    @Autowired
    private Environment env;

    @Autowired
    private DataSourceProperties properties;

    @Bean
    @Primary
    public DataSource primaryDatasource() {
        //資料庫連結
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(properties.getUrl());
        mysqlXADataSource.setUser(properties.getUsername());
        mysqlXADataSource.setPassword(properties.getPassword());
        mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
        //事務管理
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("dataSource");
        return atomikosDataSourceBean;
    }

    @Bean
    public DataSource backDatasource() {
        //資料庫連結
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(env.getProperty("spring.backdatasource.url"));
        mysqlXADataSource.setUser(env.getProperty("spring.backdatasource.username"));
        mysqlXADataSource.setPassword(env.getProperty("spring.backdatasource.password"));
        mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
        //事務管理
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("backDataSource");
        return atomikosDataSourceBean;
    }

    @Bean("primaryTemplate")
    public JdbcTemplate primaryTemplate(){
        return new JdbcTemplate(primaryDatasource());
    }

    @Bean("backTemplate")
    public JdbcTemplate batchTemplate(){
        return new JdbcTemplate(backDatasource());
    }

}

4、建立一個Service實現,模擬兩種不同的情況。

@Service
@Slf4j
public class CatTestServiceImpl extends ServiceImpl<CatTestMapper, CatTest> implements CatTestService {

    @Autowired
    private JdbcTemplate primaryTemplate;
    @Autowired
    private JdbcTemplate backTemplate;

    @Override
    @Transactional
    public void tx() {
        // 修改test1庫中的資料
        primaryTemplate.update("update user set age = ? where name = ?", 40, "aaa");
        // 修改test2庫中的資料
        backTemplate.update("update user set age = ? where name = ?", 40, "aaa");
    }

    @Override
    @Transactional
    public void tx2() {
        // 修改test1庫中的資料
        primaryTemplate.update("update user set age = ? where name = ?", 50, "aaa");
        // 模擬:修改test2庫之前丟擲異常
        throw new RuntimeException();
    }
}

這裡tx函數,是兩句update操作,一般都會成功;而tx2函數中,我們人為的製造了一個異常,這個異常是在test1庫中的資料更新後才產生的,這樣就可以測試一下test1更新成功,之後是否還能在JTA的幫助下實現回滾。

5、建立測試類,編寫測試用例

package com.sgcc.qfjs.hsf;

import com.sgcc.qfjs.module.service.CatTestService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class AppTest {

    @Autowired
    private CatTestService catTestService;

    @Test
    public void test1() throws Exception {
        // 正確更新的情況
        catTestService.tx();
    }

    @Test
    public void test2() throws Exception {
        // 更新失敗的情況
        try {
            catTestService.tx2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

這裡有兩個測試用例:

test1:因為沒有故意製造的異常,不出意外兩個庫的update都會成功,所以根據name=aaa去把兩個資料查出來,看age是否都被更新到了40。

test2:tx2函數會把test1中name=aaa的使用者age更新為50,然後丟擲異常,JTA事務生效的話,會把age回滾回40,所以這裡的檢查也是兩個庫的aaa使用者的age應該都為50,這樣就意味著JTA事務生效,保證了test1和test2兩個庫中的User表資料更新一致,沒有製造出髒資料。

6、測試驗證

執行test1成功,檢視資料庫資料是否更新成功

執行test2成功,檢視資料庫資料是否回滾成功

2022-07-28 11:09:04.999|DEBUG|main|com.atomikos.logging.Slf4jLogger|Line:32| XAResource.rollback ( 6A74614D616E61676572313635383937373734343831353030303031:6A74614D616E6167657231 ) on resource dataSource represented by XAResource instance com.mysql.jdbc.jdbc2.optional.JDBC4SuspendableXAConnection@339b45f8
2022-07-28 11:09:05.010|DEBUG|main|com.atomikos.logging.Slf4jLogger|Line:32| rollback() done of transaction jtaManager165897774481500001
2022-07-28 11:09:05.010|DEBUG|main|com.atomikos.logging.Slf4jLogger|Line:32| rollback() done of transaction jtaManager165897774481500001

到此這篇關於SpringBoot2使用JTA元件實現基於JdbcTemplate多資料來源事務管理(親測好用)的文章就介紹到這了,更多相關SpringBoot2多資料來源事務管理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com