SpringBoot使用JTA實現對多數據源的事務管理
JTA
JTA(Java Transaction API)是Java平臺上用于管理分布式事務的API。它提供了一組接口和類,用于協調和控制跨多個資源(如數據庫、消息隊列等)的事務操作。
JTA的架構體系如下:
JTA的主要目標是確保分布式環(huán)境中的事務的原子性、一致性、隔離性和持久性(ACID屬性)。它通過以下幾個關鍵概念和組件來實現:
- 事務管理器(Transaction Manager) :負責協調和管理事務的開始、提交和回滾等操作。它是JTA的核心組件,負責跟蹤和控制事務的狀態(tài)。
- 用戶事務(User Transaction) :表示應用程序發(fā)起的事務,通過事務管理器來管理和控制。
- XA資源管理器(XA Resource Manager) :表示分布式環(huán)境中的資源,如數據庫、消息隊列等。它實現了XA接口,可以參與到分布式事務中。
- XA事務(XA Transaction) :表示跨多個XA資源管理器的分布式事務。它遵循XA協議,通過兩階段提交(Two-Phase Commit)來保證事務的一致性。
使用JTA,開發(fā)人員可以在分布式環(huán)境中編寫具有事務保證的應用程序。它提供了一種標準化的方式來處理分布式事務,簡化了開發(fā)人員的工作,同時確保了數據的一致性和可靠性。
JTA事務比我們常用的JDBC事務更加強大,一個JTA事務可以有多個參與者,而一個JDBC事務則別限定在一個單一的數據庫連接。
這么說吧,我舉個栗子:
我們采用多數據源的時候,假設我們對A數據源的更新與B數據源的更新具有事務性,比如:我們對訂單中創(chuàng)建一條新的訂單數據,同時我也需要在商品庫中進行相關商品的扣減庫存,假設我們對庫存進行扣減失敗了,那么我們肯定希望我們的訂單也返回到之前沒下訂單之前的狀態(tài),畢竟我下了訂單了,庫存沒減少,我這算哪門子的下了訂單。
如果這兩條數據位于一個數據庫,那么我們可以通過簡單的事務管理就可以完成操作,那么我們至此就可以結束了,但是當我們的這兩個操作要是在不同的數據庫中,那么我們該怎么辦呢?
那么我們就來測試一下:
Spring Boot中引入相關依賴:
<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> <!--重點圍繞這個依賴--> <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配置連接數據庫的相關配置:
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用來校驗我們的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庫中的數據 primaryJdbcTemplate.update("update user set age = ? where name = ?", 30, "aaa"); // 修改test2庫中的數據 secondaryJdbcTemplate.update("update user set age = ? where name = ?", 30, "aaa"); } @Transactional public void tx2() { // 修改test1庫中的數據 primaryJdbcTemplate.update("update user set age = ? where name = ?", 40, "aaa"); // 模擬:修改test2庫之前拋出異常 throw new RuntimeException(); } }
在以上操作中,我們定義tx方法中,一般會成功,但tx2方法中,我們自己給他定義了一個異常,這個是在test1數據庫更新后才會產生的,這樣就可以測試一test1更新成功后,是否還能再JTA的幫助下實現回滾。
創(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中的更新應該回滾 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:因為沒有故意制造的異常,一般情況下兩個庫的update都會成功,然后我們根據name=aaa去把兩個數據查出來,看age是否都被更新到了30。
test2:tx2函數會把test1中name=aaa的用戶age更新為40,然后拋出異常,JTA事務生效的話,會把age回滾回30,所以這里的檢查也是兩個庫的aaa用戶的age應該都為30,這樣就意味著JTA事務生效,保證了test1和test2兩個庫中的User表數據更新一致,沒有制造出臟數據。
以上就是SpringBoot使用JTA實現對多數據源的事務管理的詳細內容,更多關于SpringBoot JTA事務管理的資料請關注腳本之家其它相關文章!
相關文章
Java模擬rank/over函數實現獲取分組排名的方法詳解
這篇文章主要為大家詳細介紹了Java模擬rank()、over()函數獲取分組排名的方法設計及實現,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2023-04-04Servlet+MyBatis項目轉Spring Cloud微服務,多數據源配置修改建議
今天小編就為大家分享一篇關于Servlet+MyBatis項目轉Spring Cloud微服務,多數據源配置修改建議,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01java進階解析Springboot上傳excel存入數據庫步驟
項目需要,寫了一個,批量導入的接口。因為需要使用excel去批量導入數據,所以寫了一個例子,經過測試已經可以用于實際開發(fā),這里記錄一下2021-09-09