SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測(cè)好用)
一、JTA組件簡(jiǎn)介
什么是JTA
JTA,全稱:Java Transaction API。JTA事務(wù)比JDBC事務(wù)更強(qiáng)大。一個(gè)JTA事務(wù)可以有多個(gè)參與者,而一個(gè)JDBC事務(wù)則被限定在一個(gè)單一的數(shù)據(jù)庫(kù)連接。所以,當(dāng)我們?cè)谕瑫r(shí)操作多個(gè)數(shù)據(jù)庫(kù)的時(shí)候,使用JTA事務(wù)就可以彌補(bǔ)JDBC事務(wù)的不足。
在Spring Boot 2.x中,整合了這兩個(gè)JTA的實(shí)現(xiàn):
Atomikos:可以通過(guò)引入spring-boot-starter-jta-atomikos依賴來(lái)使用
Bitronix:可以通過(guò)引入spring-boot-starter-jta-bitronix依賴來(lái)使用
由于Bitronix自Spring Boot 2.3.0開(kāi)始不推薦使用,所以在下面的動(dòng)手環(huán)節(jié)中,我們將使用Atomikos作為例子來(lái)介紹JTA的使用。
什么是XA協(xié)議
XA協(xié)議是數(shù)據(jù)庫(kù)層面的一套分布式事務(wù)管理的規(guī)范,JTA是XA協(xié)議在Java中的實(shí)現(xiàn),多個(gè)數(shù)據(jù)庫(kù)或是消息廠商實(shí)現(xiàn)JTA接口,開(kāi)發(fā)人員只需要調(diào)用SpringJTA接口即可實(shí)現(xiàn)JTA事務(wù)管理功能。
二、SpringBoot整合JTA
準(zhǔn)備工作
這里我們將使用最基礎(chǔ)的JdbcTemplate來(lái)實(shí)現(xiàn)數(shù)據(jù)訪問(wèn),所以如果你還不會(huì)使用JdbcTemplate配置多數(shù)據(jù)源,建議先看一JdbcTemplate的多數(shù)據(jù)源配置。
場(chǎng)景設(shè)定:
假設(shè)我們有兩個(gè)庫(kù),分別為:test1和test2
這兩個(gè)庫(kù)中都有一張User表,我們希望這兩張表中的數(shù)據(jù)是一致的
假設(shè)這兩張表中都已經(jīng)有一條數(shù)據(jù):name=aaa,age=30;因?yàn)檫@兩張表中數(shù)據(jù)是一致的,所以要update的時(shí)候,就必須兩個(gè)庫(kù)中的User表更新時(shí)候,要么都成功,要么都失敗。


1、核心依賴
<!--JTA組件核心依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>2、環(huán)境配置
yml配置文件這里jtaManager的配置,在日志輸出中非常關(guān)鍵。
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.Driver3、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() {
//數(shù)據(jù)庫(kù)鏈接
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setUrl(properties.getUrl());
mysqlXADataSource.setUser(properties.getUsername());
mysqlXADataSource.setPassword(properties.getPassword());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
//事務(wù)管理
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
atomikosDataSourceBean.setUniqueResourceName("dataSource");
return atomikosDataSourceBean;
}
@Bean
public DataSource backDatasource() {
//數(shù)據(jù)庫(kù)鏈接
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);
//事務(wù)管理
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、創(chuàng)建一個(gè)Service實(shí)現(xiàn),模擬兩種不同的情況。
@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庫(kù)中的數(shù)據(jù)
primaryTemplate.update("update user set age = ? where name = ?", 40, "aaa");
// 修改test2庫(kù)中的數(shù)據(jù)
backTemplate.update("update user set age = ? where name = ?", 40, "aaa");
}
@Override
@Transactional
public void tx2() {
// 修改test1庫(kù)中的數(shù)據(jù)
primaryTemplate.update("update user set age = ? where name = ?", 50, "aaa");
// 模擬:修改test2庫(kù)之前拋出異常
throw new RuntimeException();
}
}這里tx函數(shù),是兩句update操作,一般都會(huì)成功;而tx2函數(shù)中,我們?nèi)藶榈闹圃炝艘粋€(gè)異常,這個(gè)異常是在test1庫(kù)中的數(shù)據(jù)更新后才產(chǎn)生的,這樣就可以測(cè)試一下test1更新成功,之后是否還能在JTA的幫助下實(shí)現(xiàn)回滾。
5、創(chuàng)建測(cè)試類,編寫(xiě)測(cè)試用例
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();
}
}
}這里有兩個(gè)測(cè)試用例:
test1:因?yàn)闆](méi)有故意制造的異常,不出意外兩個(gè)庫(kù)的update都會(huì)成功,所以根據(jù)name=aaa去把兩個(gè)數(shù)據(jù)查出來(lái),看age是否都被更新到了40。
test2:tx2函數(shù)會(huì)把test1中name=aaa的用戶age更新為50,然后拋出異常,JTA事務(wù)生效的話,會(huì)把a(bǔ)ge回滾回40,所以這里的檢查也是兩個(gè)庫(kù)的aaa用戶的age應(yīng)該都為50,這樣就意味著JTA事務(wù)生效,保證了test1和test2兩個(gè)庫(kù)中的User表數(shù)據(jù)更新一致,沒(méi)有制造出臟數(shù)據(jù)。
6、測(cè)試驗(yàn)證
執(zhí)行test1成功,查看數(shù)據(jù)庫(kù)數(shù)據(jù)是否更新成功



執(zhí)行test2成功,查看數(shù)據(jù)庫(kù)數(shù)據(jù)是否回滾成功



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
到此這篇關(guān)于SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測(cè)好用)的文章就介紹到這了,更多相關(guān)SpringBoot2多數(shù)據(jù)源事務(wù)管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java8函數(shù)式接口的基礎(chǔ)學(xué)習(xí)教程
這篇文章主要給大家介紹了關(guān)于Java8函數(shù)式接口基礎(chǔ)學(xué)習(xí)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
SpringBoot定時(shí)任務(wù)調(diào)度與爬蟲(chóng)的配置實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot定時(shí)任務(wù)調(diào)度與爬蟲(chóng)的實(shí)現(xiàn),使用webmagic開(kāi)發(fā)爬蟲(chóng),繼承PageProcessor接口編寫(xiě)自己的處理類,process是定制爬蟲(chóng)邏輯的核心接口,在這里編寫(xiě)抽取邏輯,具體實(shí)現(xiàn)配置過(guò)程跟隨小編一起看看吧2022-01-01
Spring Boot LocalDateTime格式化處理的示例詳解
這篇文章主要介紹了Spring Boot LocalDateTime格式化處理的示例詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
springboot集成shiro遭遇自定義filter異常的解決
這篇文章主要介紹了springboot集成shiro遭遇自定義filter異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
java高效打印一個(gè)二維數(shù)組的實(shí)例(不用遞歸,不用兩個(gè)for循環(huán))
下面小編就為大家?guī)?lái)一篇java高效打印一個(gè)二維數(shù)組的實(shí)例(不用遞歸,不用兩個(gè)for循環(huán))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
springboot filter實(shí)現(xiàn)請(qǐng)求響應(yīng)全鏈路攔截
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何結(jié)合Filter同時(shí)攔截請(qǐng)求和響應(yīng),從而實(shí)現(xiàn)??日志采集自動(dòng)化,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
Java連接MySQL數(shù)據(jù)庫(kù)并實(shí)現(xiàn)數(shù)據(jù)交互功能
在現(xiàn)代應(yīng)用中,數(shù)據(jù)庫(kù)是不可或缺的一部分,Java 作為一種廣泛使用的編程語(yǔ)言,提供了豐富的 API 來(lái)與各種數(shù)據(jù)庫(kù)進(jìn)行交互,本文將詳細(xì)介紹如何在 Java 中連接 MySQL 數(shù)據(jù)庫(kù),并實(shí)現(xiàn)基本的數(shù)據(jù)交互功能,需要的朋友可以參考下2024-10-10

