在一个Spring Boot项目中,连接多个数据源还是比较常见的。之前也介绍了如何在几种常用框架的场景下配置多数据源,具体可见:
- Spring Boot 2.x基础教程:JdbcTemplate的多数据源配置
- Spring Boot 2.x基础教程:Spring Data JPA的多数据源配置
- Spring Boot 2.x基础教程:MyBatis的多数据源配置
当我们采用多数据源的时候,同时也会出现一个这样的特殊场景:我们希望对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表更新时候,要么都成功,要么都失败。
本文首发:blog.didispace.com/spring-boot… ,后期修改更新主要以原文为主。
操作详细
- 在
pom.xml
中加入JTA的实现Atomikos的Starter
1 | xml复制代码<dependency> |
- 在
application.properties
配置文件中配置两个test1和test2数据源
1 | properties复制代码spring.jta.enabled=true |
- 创建多数据源配置类
1 | java复制代码@Configuration |
注意,这里除了家在的配置不同之外,DataSource
也采用了AtomikosDataSourceBean
注意与之前配置多数据源使用的配置和实现类的区别。
- 创建一个Service实现,模拟两种不同的情况。
1 | java复制代码@Service |
这里tx函数,是两句update操作,一般都会成功;而tx2函数中,我们人为的制造了一个异常,这个异常是在test1库中的数据更新后才产生的,这样就可以测试一下test1更新成功,之后是否还能在JTA的帮助下实现回滚。
- 创建测试类,编写测试用例
1 | java复制代码@SpringBootTest(classes = Chapter312Application.class) |
这里有两个测试用例:
- test1:因为没有故意制造的异常,不出意外两个库的update都会成功,所以根据name=aaa去把两个数据查出来,看age是否都被更新到了30。
- test2:tx2函数会把test1中name=aaa的用户age更新为40,然后抛出异常,JTA事务生效的话,会把age回滚回30,所以这里的检查也是两个库的aaa用户的age应该都为30,这样就意味着JTA事务生效,保证了test1和test2两个库中的User表数据更新一致,没有制造出脏数据。
测试验证
将上面编写的单元测试运行起来:
观察一下启动阶段的日志,可以看到这些Atomikos初始化日志输出:
1 | yaml复制代码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 |
同时,我们在transaction-logs
目录下,还能找到关于事务的日志信息:
1 | json复制代码{"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"}]} |
更多本系列免费教程连载「点击进入汇总目录」
代码示例
本文的相关例子可以查看下面仓库中的chapter3-12
目录:
- Github:github.com/dyc87112/Sp…
- Gitee:gitee.com/didispace/S…
如果您觉得本文不错,欢迎Star
支持,您的关注是我坚持的动力!
欢迎关注我的公众号:程序猿DD,获得独家整理的学习资源、日常干货及福利赠送。
本文转载自: 掘金