java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot JTA事务管理

SpringBoot使用JTA实现对多数据源的事务管理

作者:miaowYHu

了解事务的都知道,在我们日常开发中单单靠事务管理就可以解决绝大多数问题了,但是为啥还要提出JTA这个玩意呢,到底JTA是什么呢?他又是具体来解决啥问题的呢?本文小编就给大家介绍一下如何在Spring Boot中使用JTA实现对多数据源的事务管理

JTA

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

JTA的架构体系如下:

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

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

这么说吧,我举个栗子:

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

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

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

		<!--重点围绕这个依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jta-atomikos</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

之后再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
@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);
    }

}

创建一个测试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();
    }
}

在以上操作中,我们定义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"));
        }
    }
}

对以上测试用例:

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

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

以上就是SpringBoot使用JTA实现对多数据源的事务管理的详细内容,更多关于SpringBoot JTA事务管理的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文