SpringBoot使用JTA實(shí)現(xiàn)對多數(shù)據(jù)源的事務(wù)管理
JTA
JTA(Java Transaction API)是Java平臺上用于管理分布式事務(wù)的API。它提供了一組接口和類,用于協(xié)調(diào)和控制跨多個資源(如數(shù)據(jù)庫、消息隊(duì)列等)的事務(wù)操作。
JTA的架構(gòu)體系如下:

JTA的主要目標(biāo)是確保分布式環(huán)境中的事務(wù)的原子性、一致性、隔離性和持久性(ACID屬性)。它通過以下幾個關(guān)鍵概念和組件來實(shí)現(xiàn):
- 事務(wù)管理器(Transaction Manager) :負(fù)責(zé)協(xié)調(diào)和管理事務(wù)的開始、提交和回滾等操作。它是JTA的核心組件,負(fù)責(zé)跟蹤和控制事務(wù)的狀態(tài)。
- 用戶事務(wù)(User Transaction) :表示應(yīng)用程序發(fā)起的事務(wù),通過事務(wù)管理器來管理和控制。
- XA資源管理器(XA Resource Manager) :表示分布式環(huán)境中的資源,如數(shù)據(jù)庫、消息隊(duì)列等。它實(shí)現(xiàn)了XA接口,可以參與到分布式事務(wù)中。
- XA事務(wù)(XA Transaction) :表示跨多個XA資源管理器的分布式事務(wù)。它遵循XA協(xié)議,通過兩階段提交(Two-Phase Commit)來保證事務(wù)的一致性。
使用JTA,開發(fā)人員可以在分布式環(huán)境中編寫具有事務(wù)保證的應(yīng)用程序。它提供了一種標(biāo)準(zhǔn)化的方式來處理分布式事務(wù),簡化了開發(fā)人員的工作,同時(shí)確保了數(shù)據(jù)的一致性和可靠性。
JTA事務(wù)比我們常用的JDBC事務(wù)更加強(qiáng)大,一個JTA事務(wù)可以有多個參與者,而一個JDBC事務(wù)則別限定在一個單一的數(shù)據(jù)庫連接。
這么說吧,我舉個栗子:
我們采用多數(shù)據(jù)源的時(shí)候,假設(shè)我們對A數(shù)據(jù)源的更新與B數(shù)據(jù)源的更新具有事務(wù)性,比如:我們對訂單中創(chuàng)建一條新的訂單數(shù)據(jù),同時(shí)我也需要在商品庫中進(jìn)行相關(guān)商品的扣減庫存,假設(shè)我們對庫存進(jìn)行扣減失敗了,那么我們肯定希望我們的訂單也返回到之前沒下訂單之前的狀態(tài),畢竟我下了訂單了,庫存沒減少,我這算哪門子的下了訂單。
如果這兩條數(shù)據(jù)位于一個數(shù)據(jù)庫,那么我們可以通過簡單的事務(wù)管理就可以完成操作,那么我們至此就可以結(jié)束了,但是當(dāng)我們的這兩個操作要是在不同的數(shù)據(jù)庫中,那么我們該怎么辦呢?
那么我們就來測試一下:
Spring Boot中引入相關(guān)依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--重點(diǎn)圍繞這個依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
之后再Spring Boot application配置連接數(shù)據(jù)庫的相關(guān)配置:
spring.jta.enabled=true spring.jta.atomikos.datasource.primary.xa-properties.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC spring.jta.atomikos.datasource.primary.xa-properties.user=root spring.jta.atomikos.datasource.primary.xa-properties.password=123456 spring.jta.atomikos.datasource.primary.xa-data-source-class-name=com.mysql.cj.jdbc.MysqlXADataSource spring.jta.atomikos.datasource.primary.unique-resource-name=test1 spring.jta.atomikos.datasource.primary.max-pool-size=25 spring.jta.atomikos.datasource.primary.min-pool-size=3 spring.jta.atomikos.datasource.primary.max-lifetime=20000 spring.jta.atomikos.datasource.primary.borrow-connection-timeout=10000 spring.jta.atomikos.datasource.secondary.xa-properties.url=jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC spring.jta.atomikos.datasource.secondary.xa-properties.user=root spring.jta.atomikos.datasource.secondary.xa-properties.password=123456 spring.jta.atomikos.datasource.secondary.xa-data-source-class-name=com.mysql.cj.jdbc.MysqlXADataSource spring.jta.atomikos.datasource.secondary.unique-resource-name=test2 spring.jta.atomikos.datasource.secondary.max-pool-size=25 spring.jta.atomikos.datasource.secondary.min-pool-size=3 spring.jta.atomikos.datasource.secondary.max-lifetime=20000 spring.jta.atomikos.datasource.secondary.borrow-connection-timeout=10000
@Configuration
public class DataSourceConfiguration {
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.jta.atomikos.datasource.primary")
public DataSource primaryDataSource() {
return new AtomikosDataSourceBean();
}
@Bean
@ConfigurationProperties(prefix = "spring.jta.atomikos.datasource.secondary")
public DataSource secondaryDataSource() {
return new AtomikosDataSourceBean();
}
@Bean
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
return new JdbcTemplate(primaryDataSource);
}
@Bean
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
return new JdbcTemplate(secondaryDataSource);
}
}
創(chuàng)建一個測試Service用來校驗(yàn)我們的JTA是否可以完成我們想要的工作。
@Service
public class TestService {
private JdbcTemplate primaryJdbcTemplate;
private JdbcTemplate secondaryJdbcTemplate;
public TestService(JdbcTemplate primaryJdbcTemplate, JdbcTemplate secondaryJdbcTemplate) {
this.primaryJdbcTemplate = primaryJdbcTemplate;
this.secondaryJdbcTemplate = secondaryJdbcTemplate;
}
@Transactional
public void tx() {
// 修改test1庫中的數(shù)據(jù)
primaryJdbcTemplate.update("update user set age = ? where name = ?", 30, "aaa");
// 修改test2庫中的數(shù)據(jù)
secondaryJdbcTemplate.update("update user set age = ? where name = ?", 30, "aaa");
}
@Transactional
public void tx2() {
// 修改test1庫中的數(shù)據(jù)
primaryJdbcTemplate.update("update user set age = ? where name = ?", 40, "aaa");
// 模擬:修改test2庫之前拋出異常
throw new RuntimeException();
}
}
在以上操作中,我們定義tx方法中,一般會成功,但tx2方法中,我們自己給他定義了一個異常,這個是在test1數(shù)據(jù)庫更新后才會產(chǎn)生的,這樣就可以測試一test1更新成功后,是否還能再JTA的幫助下實(shí)現(xiàn)回滾。
創(chuàng)建一個單元測試類:
@SpringBootTest(classes = Application.class)
public class ApplicationTests {
@Autowired
protected JdbcTemplate primaryJdbcTemplate;
@Autowired
protected JdbcTemplate secondaryJdbcTemplate;
@Autowired
private TestService testService;
@Test
public void test1() throws Exception {
// 正確更新的情況
testService.tx();
Assertions.assertEquals(30, primaryJdbcTemplate.queryForObject("select age from user where name=?", Integer.class, "aaa"));
Assertions.assertEquals(30, secondaryJdbcTemplate.queryForObject("select age from user where name=?", Integer.class, "aaa"));
}
@Test
public void test2() throws Exception {
// 更新失敗的情況
try {
testService.tx2();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 部分更新失敗,test1中的更新應(yīng)該回滾
Assertions.assertEquals(30, primaryJdbcTemplate.queryForObject("select age from user where name=?", Integer.class, "aaa"));
Assertions.assertEquals(30, secondaryJdbcTemplate.queryForObject("select age from user where name=?", Integer.class, "aaa"));
}
}
}
對以上測試用例:
test1:因?yàn)闆]有故意制造的異常,一般情況下兩個庫的update都會成功,然后我們根據(jù)name=aaa去把兩個數(shù)據(jù)查出來,看age是否都被更新到了30。
test2:tx2函數(shù)會把test1中name=aaa的用戶age更新為40,然后拋出異常,JTA事務(wù)生效的話,會把a(bǔ)ge回滾回30,所以這里的檢查也是兩個庫的aaa用戶的age應(yīng)該都為30,這樣就意味著JTA事務(wù)生效,保證了test1和test2兩個庫中的User表數(shù)據(jù)更新一致,沒有制造出臟數(shù)據(jù)。
以上就是SpringBoot使用JTA實(shí)現(xiàn)對多數(shù)據(jù)源的事務(wù)管理的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot JTA事務(wù)管理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java編程實(shí)現(xiàn)提取文章中關(guān)鍵字的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)提取文章中關(guān)鍵字的方法,較為詳細(xì)的分析了Java提取文章關(guān)鍵字的原理與具體實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
微服務(wù)springcloud 03.Eureka實(shí)現(xiàn)高可用的過程
這篇文章主要介紹了微服務(wù)springcloud 03.Eureka實(shí)現(xiàn)高可用的相關(guān)資料,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07
springboot中request和response的加解密實(shí)現(xiàn)代碼
這篇文章主要介紹了springboot中request和response的加解密實(shí)現(xiàn),在springboot中提供了RequestBodyAdviceAdapter和ResponseBodyAdvice,利用這兩個工具可以非常方便的對請求和響應(yīng)進(jìn)行預(yù)處理,需要的朋友可以參考下2022-06-06
springmvc實(shí)現(xiàn)json交互-requestBody和responseBody
本文主要介紹了springmvc實(shí)現(xiàn)json交互-requestBody和responseBody的相關(guān)知識。具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03

