• 在Spring Boot中使用JTA实现对多数据源的事务管理


    了解事务的都知道,在我们日常开发中单单靠事务管理就可以解决绝大多数问题了,但是为啥还要提出JTA这个玩意呢,到底JTA是什么呢?他又是具体来解决啥问题的呢?

    JTA

    JTA(Java Transaction API)是Java平台上用于管理分布式事务的API。它提供了一组接口和类,用于协调和控制跨多个资源(如数据库、消息队列等)的事务操作

    JTA的架构体系如下:
    在这里插入图片描述

    JTA的主要目标是确保分布式环境中的事务的原子性、一致性、隔离性和持久性(ACID属性)。它通过以下几个关键概念和组件来实现:

    • 事务管理器(Transaction Manager):负责协调和管理事务的开始、提交和回滚等操作。它是JTA的核心组件,负责跟踪和控制事务的状态。

    • 用户事务(User Transaction):表示应用程序发起的事务,通过事务管理器来管理和控制。

    • XA资源管理器(XA Resource Manager):表示分布式环境中的资源,如数据库、消息队列等。它实现了XA接口,可以参与到分布式事务中。

    • XA事务(XA Transaction):表示跨多个XA资源管理器的分布式事务。它遵循XA协议,通过两阶段提交(Two-Phase Commit)来保证事务的一致性。

    使用JTA,开发人员可以在分布式环境中编写具有事务保证的应用程序。它提供了一种标准化的方式来处理分布式事务,简化了开发人员的工作,同时确保了数据的一致性和可靠性。
    JTA事务比我们常用的JDBC事务更加强大,一个JTA事务可以有多个参与者,而一个JDBC事务则别限定在一个单一的数据库连接。

    这么说吧,我举个栗子:

    我们采用多数据源的时候,假设我们对A数据源的更新与B数据源的更新具有事务性,比如:我们对订单中创建一条新的订单数据,同时我也需要在商品库中进行相关商品的扣减库存,假设我们对库存进行扣减失败了,那么我们肯定希望我们的订单也返回到之前没下订单之前的状态,毕竟我下了订单了,库存没减少,我这算哪门子的下了订单。

    如果这两条数据位于一个数据库,那么我们可以通过简单的事务管理就可以完成操作,那么我们至此就可以结束了,但是当我们的这两个操作要是在不同的数据库中,那么我们该怎么办呢?

    那么我们就来测试一下:
    Spring Boot中引入相关依赖:

    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-webartifactId>
    		dependency>
    
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-jdbcartifactId>
    		dependency>
    
    		
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-jta-atomikosartifactId>
    		dependency>
    
    		<dependency>
    			<groupId>mysqlgroupId>
    			<artifactId>mysql-connector-javaartifactId>
    		dependency>
    
    		<dependency>
    			<groupId>org.projectlombokgroupId>
    			<artifactId>lombokartifactId>
    			<scope>providedscope>
    		dependency>
    
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-testartifactId>
    			<scope>testscope>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    之后再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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    @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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    创建一个测试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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在以上操作中,我们定义tx方法中,一般会成功,但tx2方法中,我们自己给他定义了一个异常,这个是在test1数据库更新后才会产生的,这样就可以测试一test1更新成功后,是否还能再JTA的帮助下实现回滚。

    创建一个单元测试类:

    @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"));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    对以上测试用例:

    test1:因为没有故意制造的异常,一般情况下两个库的update都会成功,然后我们根据name=aaa去把两个数据查出来,看age是否都被更新到了30。

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

  • 相关阅读:
    【Selenium & Other】一键杀死进程 & 进程清理大师
    简单聊聊大数据
    java 类和对象 属性和行为
    视频教程下载:用ChatGPT的 API 开发AI应用指南
    python趣味编程-5分钟实现一个F1 赛车公路游戏(含源码、步骤讲解)
    【万字长文】使用 LSM Tree 思想实现一个 KV 数据库
    HELM 如何使用模板变量部署多个应用
    RabbitMQ学习总结-基础篇
    【无标题】
    LeetCode-1402题解
  • 原文地址:https://blog.csdn.net/qq_45922256/article/details/134345453