SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測好用)
一、JTA組件簡介
什么是JTA
JTA,全稱:Java Transaction API。JTA事務(wù)比JDBC事務(wù)更強(qiáng)大。一個(gè)JTA事務(wù)可以有多個(gè)參與者,而一個(gè)JDBC事務(wù)則被限定在一個(gè)單一的數(shù)據(jù)庫連接。所以,當(dāng)我們?cè)谕瑫r(shí)操作多個(gè)數(shù)據(jù)庫的時(shí)候,使用JTA事務(wù)就可以彌補(bǔ)JDBC事務(wù)的不足。
在Spring Boot 2.x中,整合了這兩個(gè)JTA的實(shí)現(xiàn):
Atomikos:可以通過引入spring-boot-starter-jta-atomikos依賴來使用
Bitronix:可以通過引入spring-boot-starter-jta-bitronix依賴來使用
由于Bitronix自Spring Boot 2.3.0開始不推薦使用,所以在下面的動(dòng)手環(huán)節(jié)中,我們將使用Atomikos作為例子來介紹JTA的使用。
什么是XA協(xié)議
XA協(xié)議是數(shù)據(jù)庫層面的一套分布式事務(wù)管理的規(guī)范,JTA是XA協(xié)議在Java中的實(shí)現(xiàn),多個(gè)數(shù)據(jù)庫或是消息廠商實(shí)現(xiàn)JTA接口,開發(fā)人員只需要調(diào)用SpringJTA接口即可實(shí)現(xiàn)JTA事務(wù)管理功能。
二、SpringBoot整合JTA
準(zhǔn)備工作
這里我們將使用最基礎(chǔ)的JdbcTemplate來實(shí)現(xiàn)數(shù)據(jù)訪問,所以如果你還不會(huì)使用JdbcTemplate配置多數(shù)據(jù)源,建議先看一JdbcTemplate的多數(shù)據(jù)源配置。
場景設(shè)定:
假設(shè)我們有兩個(gè)庫,分別為:test1和test2
這兩個(gè)庫中都有一張User表,我們希望這兩張表中的數(shù)據(jù)是一致的
假設(shè)這兩張表中都已經(jīng)有一條數(shù)據(jù):name=aaa,age=30;因?yàn)檫@兩張表中數(shù)據(jù)是一致的,所以要update的時(shí)候,就必須兩個(gè)庫中的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.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() { //數(shù)據(jù)庫鏈接 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ù)庫鏈接 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庫中的數(shù)據(jù) primaryTemplate.update("update user set age = ? where name = ?", 40, "aaa"); // 修改test2庫中的數(shù)據(jù) backTemplate.update("update user set age = ? where name = ?", 40, "aaa"); } @Override @Transactional public void tx2() { // 修改test1庫中的數(shù)據(jù) primaryTemplate.update("update user set age = ? where name = ?", 50, "aaa"); // 模擬:修改test2庫之前拋出異常 throw new RuntimeException(); } }
這里tx函數(shù),是兩句update操作,一般都會(huì)成功;而tx2函數(shù)中,我們?nèi)藶榈闹圃炝艘粋€(gè)異常,這個(gè)異常是在test1庫中的數(shù)據(jù)更新后才產(chǎn)生的,這樣就可以測試一下test1更新成功,之后是否還能在JTA的幫助下實(shí)現(xiàn)回滾。
5、創(chuàng)建測試類,編寫測試用例
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è)測試用例:
test1:因?yàn)闆]有故意制造的異常,不出意外兩個(gè)庫的update都會(huì)成功,所以根據(jù)name=aaa去把兩個(gè)數(shù)據(jù)查出來,看age是否都被更新到了40。
test2:tx2函數(shù)會(huì)把test1中name=aaa的用戶age更新為50,然后拋出異常,JTA事務(wù)生效的話,會(huì)把a(bǔ)ge回滾回40,所以這里的檢查也是兩個(gè)庫的aaa用戶的age應(yīng)該都為50,這樣就意味著JTA事務(wù)生效,保證了test1和test2兩個(gè)庫中的User表數(shù)據(jù)更新一致,沒有制造出臟數(shù)據(jù)。
6、測試驗(yàn)證
執(zhí)行test1成功,查看數(shù)據(jù)庫數(shù)據(jù)是否更新成功
執(zhí)行test2成功,查看數(shù)據(jù)庫數(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ù)管理(親測好用)的文章就介紹到這了,更多相關(guān)SpringBoot2多數(shù)據(jù)源事務(wù)管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 如何讀取Excel格式xls、xlsx數(shù)據(jù)工具類
這篇文章主要介紹了Java 如何讀取Excel格式xls、xlsx數(shù)據(jù)工具類的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09詳解Java如何應(yīng)對(duì)常見的安全威脅和攻擊類型
隨著信息技術(shù)的快速發(fā)展,網(wǎng)絡(luò)安全問題日益突出,本文將以Java開發(fā)語言為例,深入探討網(wǎng)絡(luò)協(xié)議的安全性問題,通過分析常見的安全威脅和攻擊類型,設(shè)計(jì)和實(shí)施安全協(xié)議等主題,為讀者提供一些有益的思路和方法,需要的朋友可以參考下2023-11-11Java 程序里transient關(guān)鍵字使用方法示例
這篇文章主要為大家介紹了Java 程序里transient關(guān)鍵字使用方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11源碼分析Java中ThreadPoolExecutor的底層原理
這篇文章主要帶大家從源碼分析一下Java中ThreadPoolExecutor的底層原理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-05-05Java mockito單元測試實(shí)現(xiàn)過程解析
這篇文章主要介紹了Java mockito單元測試實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Java ArrayList的基本概念和作用及動(dòng)態(tài)數(shù)組的機(jī)制與性能
在Java中,ArrayList是一個(gè)實(shí)現(xiàn)了List接口的動(dòng)態(tài)數(shù)組,它可以根據(jù)需要自動(dòng)增加大小,因此可以存儲(chǔ)任意數(shù)量的元素,這篇文章主要介紹了探秘Java ArrayList的基本概念和作用及動(dòng)態(tài)數(shù)組的機(jī)制與性能,需要的朋友可以參考下2023-12-12Java中Future和FutureTask的示例詳解及使用
Java中的Future和FutureTask通常和線程池搭配使用,用來獲取線程池返回執(zhí)行后的返回值,下面這篇文章主要給大家介紹了關(guān)于Java中Future和FutureTask使用的相關(guān)資料,需要的朋友可以參考下2021-11-11