<noframes id="bhrfl"><address id="bhrfl"></address>

    <address id="bhrfl"></address>

    <noframes id="bhrfl"><address id="bhrfl"><th id="bhrfl"></th></address>

    <form id="bhrfl"><th id="bhrfl"><progress id="bhrfl"></progress></th></form>

    <em id="bhrfl"><span id="bhrfl"></span></em>

    全部
    常見問題
    產品動態
    精選推薦

    Spring Boot 2.x基礎教程:使用JTA實現多數據源的事務管理

    管理 管理 編輯 刪除

    什么是JTA

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

    在Spring Boot 2.x中,整合了這兩個JTA的實現:

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

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

    #動手試試

    下面我們就來實操一下,如何在Spring Boot中使用JTA來實現多數據源下的事務管理。

    準備工作

    1. 這里我們將使用最基礎的JdbcTemplate來實現數據訪問,所以如果你還不會使用JdbcTemplate配置多數據源,建議先看一下JdbcTemplate的多數據源配置。
    2. 場景設定:
    • 假設我們有兩個庫,分別為:test1和test2
    • 這兩個庫中都有一張User表,我們希望這兩張表中的數據是一致的
    • 假設這兩張表中都已經有一條數據:name=aaa,age=30;因為這兩張表中數據是一致的,所以要update的時候,就必須兩個庫中的User表更新時候,要么都成功,要么都失敗。

    操作詳細

    1. pom.xml中加入JTA的實現Atomikos的Starter
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    </dependency>
    
    1. application.properties配置文件中配置兩個test1和test2數據源
    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. 創建多數據源配置類
    @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注意與之前配置多數據源使用的配置和實現類的區別。

    1. 創建一個Service實現,模擬兩種不同的情況。
    @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函數,是兩句update操作,一般都會成功;而tx2函數中,我們人為的制造了一個異常,這個異常是在test1庫中的數據更新后才產生的,這樣就可以測試一下test1更新成功,之后是否還能在JTA的幫助下實現回滾。

    1. 創建測試類,編寫測試用例
    @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中的更新應該回滾
                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表數據更新一致,沒有制造出臟數據。

    #測試驗證

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

    觀察一下啟動階段的日志,可以看到這些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目錄下,還能找到關于事務的日志信息:

    {"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"}]}
    

    本系列教程《Spring Boot 2.x基礎教程》點擊直達!。學習過程中如遇困難,建議加入Spring技術交流群open in new window,參與交流與討論,更好的學習與進步!

    #代碼示例

    本文的相關例子可以查看下面倉庫中的chapter3-12目錄:

    請登錄后查看

    CRMEB 最后編輯于2025-02-25 14:58:48

    快捷回復
    回復
    回復
    回復({{post_count}}) {{!is_user ? '我的回復' :'全部回復'}}
    排序 默認正序 回復倒序 點贊倒序

    {{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level }}

    作者 管理員 企業

    {{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推薦': '推薦'}}
    {{item.is_suggest == 1? '取消推薦': '推薦'}}
    沙發 板凳 地板 {{item.floor}}#
    {{item.user_info.title || '暫無簡介'}}
    附件

    {{itemf.name}}

    {{item.created_at}}  {{item.ip_address}}
    打賞
    已打賞¥{{item.reward_price}}
    {{item.like_count}}
    {{item.showReply ? '取消回復' : '回復'}}
    刪除
    回復
    回復

    {{itemc.user_info.nickname}}

    {{itemc.user_name}}

    回復 {{itemc.comment_user_info.nickname}}

    附件

    {{itemf.name}}

    {{itemc.created_at}}
    打賞
    已打賞¥{{itemc.reward_price}}
    {{itemc.like_count}}
    {{itemc.showReply ? '取消回復' : '回復'}}
    刪除
    回復
    回復
    查看更多
    打賞
    已打賞¥{{reward_price}}
    787
    {{like_count}}
    {{collect_count}}
    添加回復 ({{post_count}})

    相關推薦

    快速安全登錄

    使用微信掃碼登錄
    {{item.label}} 加精
    {{item.label}} {{item.label}} 板塊推薦 常見問題 產品動態 精選推薦 首頁頭條 首頁動態 首頁推薦
    取 消 確 定
    回復
    回復
    問題:
    問題自動獲取的帖子內容,不準確時需要手動修改. [獲取答案]
    答案:
    提交
    bug 需求 取 消 確 定
    打賞金額
    當前余額:¥{{rewardUserInfo.reward_price}}
    {{item.price}}元
    請輸入 0.1-{{reward_max_price}} 范圍內的數值
    打賞成功
    ¥{{price}}
    完成 確認打賞

    微信登錄/注冊

    切換手機號登錄

    {{ bind_phone ? '綁定手機' : '手機登錄'}}

    {{codeText}}
    切換微信登錄/注冊
    暫不綁定
    亚洲欧美字幕
    CRMEB客服

    CRMEB咨詢熱線 咨詢熱線

    400-8888-794

    微信掃碼咨詢

    CRMEB開源商城下載 源碼下載 CRMEB幫助文檔 幫助文檔
    返回頂部 返回頂部
    CRMEB客服