Spring Boot 2.x基础教程:使用JTA实现分布式事务

2025-05-27 0 76

Spring Boot 2.x基础教程:使用JTA实现分布式事务

在一个Spring Boot项目中,连接多个数据源还是比较常见的。

当我们采用多数据源的时候,同时也会出现一个这样的特殊场景:我们希望对A数据源的更新和B数据源的更新具备事务性。这样的例子很常见,比如:在订单库中创建一条订单记录,同时还需要在商品库中扣减商品库存。如果库存扣减失败,那么我们希望订单创建也能够回滚。

如果这两条数据在一个数据库中,那么通过之前介绍的事务管理就能轻松解决了。但是,当这两个操作位于不同的数据库中,那么就无法实现了。

本文就来介绍一种解决这类问题的方法: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来实现多数据源下的事务管理。

准备工作

  • 这里我们将使用最基础的JdbcTemplate来实现数据访问,所以如果你还不会使用JdbcTemplate配置多数据源,建议先看一下JdbcTemplate的多数据源配置。

场景设定:

  • 假设我们有两个库,分别为:test1和test2
  • 这两个库中都有一张User表,我们希望这两张表中的数据是一致的
  • 假设这两张表中都已经有一条数据:name=aaa,age=30;因为这两张表中数据是一致的,所以要update的时候,就必须两个库中的User表更新时候,要么都成功,要么都失败。

操作详细

在pom.xml中加入JTA的实现Atomikos的Starter

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-jta-atomikos</artifactId>
  4. </dependency>

在application.properties配置文件中配置两个test1和test2数据源

  1. spring.jta.enabled=true
  2. spring.jta.atomikos.datasource.primary.xa-properties.url=jdbc:mysql://localhost:3306/test1
  3. spring.jta.atomikos.datasource.primary.xa-properties.user=root
  4. spring.jta.atomikos.datasource.primary.xa-properties.password=12345678
  5. spring.jta.atomikos.datasource.primary.xa-data-source-class-name=com.mysql.cj.jdbc.MysqlXADataSource
  6. spring.jta.atomikos.datasource.primary.unique-resource-name=test1
  7. spring.jta.atomikos.datasource.primary.max-pool-size=25
  8. spring.jta.atomikos.datasource.primary.min-pool-size=3
  9. spring.jta.atomikos.datasource.primary.max-lifetime=20000
  10. spring.jta.atomikos.datasource.primary.borrow-connection-timeout=10000
  11. spring.jta.atomikos.datasource.secondary.xa-properties.url=jdbc:mysql://localhost:3306/test2
  12. spring.jta.atomikos.datasource.secondary.xa-properties.user=root
  13. spring.jta.atomikos.datasource.secondary.xa-properties.password=12345678
  14. spring.jta.atomikos.datasource.secondary.xa-data-source-class-name=com.mysql.cj.jdbc.MysqlXADataSource
  15. spring.jta.atomikos.datasource.secondary.unique-resource-name=test2
  16. spring.jta.atomikos.datasource.secondary.max-pool-size=25
  17. spring.jta.atomikos.datasource.secondary.min-pool-size=3
  18. spring.jta.atomikos.datasource.secondary.max-lifetime=20000
  19. spring.jta.atomikos.datasource.secondary.borrow-connection-timeout=10000

创建多数据源配置类

  1. @Configuration
  2. publicclassDataSourceConfiguration{
  3. @Primary
  4. @Bean
  5. @ConfigurationProperties(prefix="spring.jta.atomikos.datasource.primary")
  6. publicDataSourceprimaryDataSource(){
  7. returnnewAtomikosDataSourceBean();
  8. }
  9. @Bean
  10. @ConfigurationProperties(prefix="spring.jta.atomikos.datasource.secondary")
  11. publicDataSourcesecondaryDataSource(){
  12. returnnewAtomikosDataSourceBean();
  13. }
  14. @Bean
  15. publicJdbcTemplateprimaryJdbcTemplate(@Qualifier("primaryDataSource")DataSourceprimaryDataSource){
  16. returnnewJdbcTemplate(primaryDataSource);
  17. }
  18. @Bean
  19. publicJdbcTemplatesecondaryJdbcTemplate(@Qualifier("secondaryDataSource")DataSourcesecondaryDataSource){
  20. returnnewJdbcTemplate(secondaryDataSource);
  21. }
  22. }

注意,这里除了家在的配置不同之外,DataSource也采用了AtomikosDataSourceBean注意与之前配置多数据源使用的配置和实现类的区别。

创建一个Service实现,模拟两种不同的情况。

  1. @Service
  2. publicclassTestService{
  3. privateJdbcTemplateprimaryJdbcTemplate;
  4. privateJdbcTemplatesecondaryJdbcTemplate;
  5. publicTestService(JdbcTemplateprimaryJdbcTemplate,JdbcTemplatesecondaryJdbcTemplate){
  6. this.primaryJdbcTemplate=primaryJdbcTemplate;
  7. this.secondaryJdbcTemplate=secondaryJdbcTemplate;
  8. }
  9. @Transactional
  10. publicvoidtx(){
  11. //修改test1库中的数据
  12. primaryJdbcTemplate.update("updateusersetage=?wherename=?",30,"aaa");
  13. //修改test2库中的数据
  14. secondaryJdbcTemplate.update("updateusersetage=?wherename=?",30,"aaa");
  15. }
  16. @Transactional
  17. publicvoidtx2(){
  18. //修改test1库中的数据
  19. primaryJdbcTemplate.update("updateusersetage=?wherename=?",40,"aaa");
  20. //模拟:修改test2库之前抛出异常
  21. thrownewRuntimeException();
  22. }
  23. }

这里tx函数,是两句update操作,一般都会成功;而tx2函数中,我们人为的制造了一个异常,这个异常是在test1库中的数据更新后才产生的,这样就可以测试一下test1更新成功,之后是否还能在JTA的帮助下实现回滚。

创建测试类,编写测试用例

  1. @SpringBootTest(classes=Chapter312Application.class)
  2. publicclassChapter312ApplicationTests{
  3. @Autowired
  4. protectedJdbcTemplateprimaryJdbcTemplate;
  5. @Autowired
  6. protectedJdbcTemplatesecondaryJdbcTemplate;
  7. @Autowired
  8. privateTestServicetestService;
  9. @Test
  10. publicvoidtest1()throwsException{
  11. //正确更新的情况
  12. testService.tx();
  13. Assertions.assertEquals(30,primaryJdbcTemplate.queryForObject("selectagefromuserwherename=?",Integer.class,"aaa"));
  14. Assertions.assertEquals(30,secondaryJdbcTemplate.queryForObject("selectagefromuserwherename=?",Integer.class,"aaa"));
  15. }
  16. @Test
  17. publicvoidtest2()throwsException{
  18. //更新失败的情况
  19. try{
  20. testService.tx2();
  21. }catch(Exceptione){
  22. e.printStackTrace();
  23. }finally{
  24. //部分更新失败,test1中的更新应该回滚
  25. Assertions.assertEquals(30,primaryJdbcTemplate.queryForObject("selectagefromuserwherename=?",Integer.class,"aaa"));
  26. Assertions.assertEquals(30,secondaryJdbcTemplate.queryForObject("selectagefromuserwherename=?",Integer.class,"aaa"));
  27. }
  28. }
  29. }

这里有两个测试用例:

  • test1:因为没有故意制造的异常,不出意外两个库的update都会成功,所以根据name=aaa去把两个数据查出来,看age是否都被更新到了30。
  • test2:tx2函数会把test1中name=aaa的用户age更新为40,然后抛出异常,JTA事务生效的话,会把age回滚回30,所以这里的检查也是两个库的aaa用户的age应该都为30,这样就意味着JTA事务生效,保证了test1和test2两个库中的User表数据更新一致,没有制造出脏数据。

测试验证

将上面编写的单元测试运行起来:

Spring Boot 2.x基础教程:使用JTA实现分布式事务

观察一下启动阶段的日志,可以看到这些Atomikos初始化日志输出:

  1. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807
  2. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.allow_subtransactions=true
  3. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.recovery_delay=10000
  4. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.automatic_resource_registration=true
  5. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.oltp_max_retries=5
  6. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.client_demarcation=false
  7. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.threaded_2pc=false
  8. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.serial_jta_transactions=true
  9. 2021-02-0219:00:36.145INFO8868—[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
  10. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.rmi_export_class=none
  11. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.max_actives=50
  12. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.checkpoint_interval=500
  13. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.enable_logging=true
  14. 2021-02-0219:00:36.145INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.log_base_name=tmlog
  15. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.max_timeout=300000
  16. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.trust_client_tm=false
  17. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory
  18. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.tm_unique_name=127.0.0.1.tm
  19. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
  20. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.oltp_retry_interval=10000
  21. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:java.naming.provider.url=rmi://localhost:1099
  22. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.force_shutdown_on_vm_exit=false
  23. 2021-02-0219:00:36.146INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:USING:com.atomikos.icatch.default_jta_timeout=10000
  24. 2021-02-0219:00:36.147INFO8868—[main]c.a.icatch.provider.imp.AssemblerImp:Usingdefault(local)loggingandrecovery…
  25. 2021-02-0219:00:36.184INFO8868—[main]c.a.d.xa.XATransactionalResource:test1:refreshedXAResource
  26. 2021-02-0219:00:36.203INFO8868—[main]c.a.d.xa.XATransactionalResource

同时,我们在transaction-logs目录下,还能找到关于事务的日志信息:

Spring Boot 2.x基础教程:使用JTA实现分布式事务

  1. {"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"}]}
  2. {"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"}]}
  3. {"id":"127.0.0.1.tm161226409092800002","wasCommitted":false,"participants":[{"uri":"127.0.0.1.tm3","state":"TERMINATED","expires":1612264100832,"resourceName":"test1"}]}

代码示例

本文的相关例子可以查看下面仓库中的chapter3-12目录:

Github:https://github.com/dyc87112/SpringBoot-Learning/

Gitee:https://gitee.com/didispace/SpringBoot-Learning/

原文地址:https://zhuanlan.51cto.com/art/202102/644394.htm

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 Spring Boot 2.x基础教程:使用JTA实现分布式事务 https://www.kuaiidc.com/77434.html

相关文章

发表评论
暂无评论