<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot 2.x基礎(chǔ)教程:使用JTA實現(xiàn)分布式事務(wù)

          共 10957字,需瀏覽 22分鐘

           ·

          2021-02-04 09:13


          在一個Spring Boot項目中,連接多個數(shù)據(jù)源還是比較常見的。之前也介紹了如何在幾種常用框架的場景下配置多數(shù)據(jù)源,具體可見:

          當(dāng)我們采用多數(shù)據(jù)源的時候,同時也會出現(xiàn)一個這樣的特殊場景:我們希望對A數(shù)據(jù)源的更新和B數(shù)據(jù)源的更新具備事務(wù)性。這樣的例子很常見,比如:在訂單庫中創(chuàng)建一條訂單記錄,同時還需要在商品庫中扣減商品庫存。如果庫存扣減失敗,那么我們希望訂單創(chuàng)建也能夠回滾。

          如果這兩條數(shù)據(jù)在一個數(shù)據(jù)庫中,那么通過之前介紹的事務(wù)管理就能輕松解決了。但是,當(dāng)這兩個操作位于不同的數(shù)據(jù)庫中,那么就無法實現(xiàn)了。

          本文就來介紹一種解決這類問題的方法:JTA事務(wù)。

          什么是JTA

          JTA,全稱:Java Transaction API。JTA事務(wù)比JDBC事務(wù)更強(qiáng)大。一個JTA事務(wù)可以有多個參與者,而一個JDBC事務(wù)則被限定在一個單一的數(shù)據(jù)庫連接。所以,當(dāng)我們在同時操作多個數(shù)據(jù)庫的時候,使用JTA事務(wù)就可以彌補(bǔ)JDBC事務(wù)的不足。

          在Spring Boot 2.x中,整合了這兩個JTA的實現(xiàn):

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

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

          動手試試

          下面我們就來實操一下,如何在Spring Boot中使用JTA來實現(xiàn)多數(shù)據(jù)源下的事務(wù)管理。

          準(zhǔn)備工作

          1. 這里我們將使用最基礎(chǔ)的JdbcTemplate來實現(xiàn)數(shù)據(jù)訪問,所以如果你還不會使用JdbcTemplate配置多數(shù)據(jù)源,建議先看一下JdbcTemplate的多數(shù)據(jù)源配置

          2. 場景設(shè)定:

          • 假設(shè)我們有兩個庫,分別為:test1和test2
          • 這兩個庫中都有一張User表,我們希望這兩張表中的數(shù)據(jù)是一致的
          • 假設(shè)這兩張表中都已經(jīng)有一條數(shù)據(jù):name=aaa,age=30;因為這兩張表中數(shù)據(jù)是一致的,所以要update的時候,就必須兩個庫中的User表更新時候,要么都成功,要么都失敗。

          操作詳細(xì)

          1. pom.xml中加入JTA的實現(xiàn)Atomikos的Starter
          <dependency>
          ????<groupId>org.springframework.bootgroupId>
          ????<artifactId>spring-boot-starter-jta-atomikosartifactId>
          dependency>
          1. application.properties配置文件中配置兩個test1和test2數(shù)據(jù)源
          spring.jta.enabled=true

          spring.jta.atomikos.datasource.primary.xa-properties.url=jdbc:mysql://localhost:3306/test1
          spring.jta.atomikos.datasource.primary.xa-properties.user=root
          spring.jta.atomikos.datasource.primary.xa-properties.password=12345678
          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
          spring.jta.atomikos.datasource.secondary.xa-properties.user=root
          spring.jta.atomikos.datasource.secondary.xa-properties.password=12345678
          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
          1. 創(chuàng)建多數(shù)據(jù)源配置類
          @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);
          ????}

          }

          注意,這里除了家在的配置不同之外,DataSource也采用了AtomikosDataSourceBean注意與之前配置多數(shù)據(jù)源使用的配置和實現(xiàn)類的區(qū)別。

          1. 創(chuàng)建一個Service實現(xiàn),模擬兩種不同的情況。
          @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函數(shù),是兩句update操作,一般都會成功;而tx2函數(shù)中,我們?nèi)藶榈闹圃炝艘粋€異常,這個異常是在test1庫中的數(shù)據(jù)更新后才產(chǎn)生的,這樣就可以測試一下test1更新成功,之后是否還能在JTA的幫助下實現(xiàn)回滾。

          1. 創(chuàng)建測試類,編寫測試用例
          @SpringBootTest(classes?=?Chapter312Application.class)
          public?class?Chapter312ApplicationTests?
          {

          ????@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:因為沒有故意制造的異常,不出意外兩個庫的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ù)。

          測試驗證

          將上面編寫的單元測試運行起來:

          觀察一下啟動階段的日志,可以看到這些Atomikos初始化日志輸出:

          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.default_max_wait_time_on_shutdown?=?9223372036854775807
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.allow_subtransactions?=?true
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.recovery_delay?=?10000
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.automatic_resource_registration?=?true
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.oltp_max_retries?=?5
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.client_demarcation?=?false
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.threaded_2pc?=?false
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.serial_jta_transactions?=?true
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.log_base_dir?=?/Users/didi/Documents/GitHub/SpringBoot-Learning/2.x/chapter3-12/transaction-logs
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.rmi_export_class?=?none
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.max_actives?=?50
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.checkpoint_interval?=?500
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.enable_logging?=?true
          2021-02-02?19:00:36.145??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.log_base_name?=?tmlog
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.max_timeout?=?300000
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.trust_client_tm?=?false
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?java.naming.factory.initial?=?com.sun.jndi.rmi.registry.RegistryContextFactory
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.tm_unique_name?=?127.0.0.1.tm
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.forget_orphaned_log_entries_delay?=?86400000
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.oltp_retry_interval?=?10000
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?java.naming.provider.url?=?rmi://localhost:1099
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.force_shutdown_on_vm_exit?=?false
          2021-02-02?19:00:36.146??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?USING:?com.atomikos.icatch.default_jta_timeout?=?10000
          2021-02-02?19:00:36.147??INFO?8868?---?[???????????main]?c.a.icatch.provider.imp.AssemblerImp?????:?Using?default?(local)?logging?and?recovery...
          2021-02-02?19:00:36.184??INFO?8868?---?[???????????main]?c.a.d.xa.XATransactionalResource?????????:?test1:?refreshed?XAResource
          2021-02-02?19:00:36.203??INFO?8868?---?[???????????main]?c.a.d.xa.XATransactionalResource?????????:?test2:?refreshed?XAResource

          同時,我們在transaction-logs目錄下,還能找到關(guān)于事務(wù)的日志信息:

          {"id":"127.0.0.1.tm161226409083100001","wasCommitted":true,"participants":[{"uri":"127.0.0.1.tm1","state":"COMMITTING","expires":1612264100801,"resourceName":"test1"},{"uri":"127.0.0.1.tm2","state":"COMMITTING","expires":1612264100801,"resourceName":"test2"}]}
          {"id":"127.0.0.1.tm161226409083100001","wasCommitted":true,"participants":[{"uri":"127.0.0.1.tm1","state":"TERMINATED","expires":1612264100804,"resourceName":"test1"},{"uri":"127.0.0.1.tm2","state":"TERMINATED","expires":1612264100804,"resourceName":"test2"}]}
          {"id":"127.0.0.1.tm161226409092800002","wasCommitted":false,"participants":[{"uri":"127.0.0.1.tm3","state":"TERMINATED","expires":1612264100832,"resourceName":"test1"}]}

          代碼示例

          本文的相關(guān)例子可以查看下面?zhèn)}庫中的chapter3-12目錄:

          • Github:https://github.com/dyc87112/SpringBoot-Learning/
          • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

          如果您覺得本文不錯,歡迎Star支持,您的關(guān)注是我堅持的動力!


          往期推薦

          最近又Get個新技能,不是全棧也能用技術(shù)倒騰點東西賺零花錢了!

          新書上市 | 《Java性能優(yōu)化實踐》,眾多業(yè)內(nèi)大佬推薦閱讀

          Spring中涉及的設(shè)計模式總結(jié)

          微信8.0之后,又悄悄上線新功能:別人“搶不到”的紅包!趕緊看看灰度到你了沒?

          蘋果開源代碼中驚現(xiàn)“wechat”,老外注釋的吐槽亮了!




          瀏覽 81
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  激情乱伦网站 | 色也俺一区| 国产婷婷在线播放 | 91吴梦梦无码一区二区在线看 | 国产一级内射视频 |