springBoot入门总结(八)使用 jta+atomikos 整合springBoot分布式事务

一、JTA:Java Transaction Manager

事务是计算机应用中不可或缺的组件模型,它保证了用户操作的原子性 ( Atomicity )、一致性 ( Consistency )、隔离性 ( Isolation ) 和持久性 ( Durabilily )。

JTA:(Java Transaction Manager)是Java 中对事务进行管理的接口,在Java应用中,调用者实际上便是通过调用事务接口来实现事务的管理。

如果一个应用中存在多个不同的数据源,通常我们会创建多个数据源的事务管理器。

比如一个Java应用需要调用两个不同的数据源,那么我们就需要创建两个DataSourceTransactionManager分别来对数据库事务进行管理。

假如在一次服务的请求过程中,需要同时调用两个数据源,我们都知道必须要保证事务的一致性 ( Consistency )。

    public void service {
        //业务A
        transaction A
        //业务B
        transaction B
    }

假设, transaction A 提交成功后,继续执行业务B抛出了异常,但是由于transaction A已经提交了,数据库已经插入不能进行回滚操作,那么这将导致无法满足事务的一致性。

JTA就是用来解决在同时访问多个数据源时,可能出现的数据不一致问题。

JTA的特点

  1. 两阶段提交
  2. 事务时间太长,锁数据太长
  3. 低性能,低吞吐量

JTA是如何实现多数据源的事务管理呢?

主要的原理是两阶段提交以上面的请求为例,当整个业务完成了之后只是第一阶段提交,在第二阶段提交之前,会检查其他所有事务是否已经提交,如果发现整个请求过程中出现了错误或是没有提交等情况,那么第二阶段就不会提交,而是直接rollback操作,这样所有的事务都会做Rollback操作。

二、atomikos:Atomikos TransactionsEssentials

Atomikos是JTA的实现,spring boot中引入依赖:

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

三、多数据源的事务管理

1、本地数据库 test01、test02

库中分别新建表 t_user 字段 id(主键id自增)、name(姓名)、age(年龄)

2、创建项目,并将 jta-atomikos 引入pom文件。

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.1.1</version>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jta-atomikos</artifactId>
   </dependency>
   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.0</version>
   </dependency>
   <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.1.1</version>
   </dependency>
   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
         <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
         </exclusion>
      </exclusions>
   </dependency>
</dependencies>

3、application.properties配置多数据源

# Mysql 1
spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.test1.jdbc-url=jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.test1.username=root
spring.datasource.test1.password=1q2w3e4r5t
spring.datasource.test1.minPoolSize = 3
spring.datasource.test1.maxPoolSize = 25
spring.datasource.test1.maxLifetime = 20000
spring.datasource.test1.borrowConnectionTimeout = 30
spring.datasource.test1.loginTimeout = 30
spring.datasource.test1.maintenanceInterval = 60
spring.datasource.test1.maxIdleTime = 60

# Mysql 2
spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.test2.jdbc-url=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.test2.username=root
spring.datasource.test2.password=1q2w3e4r5t
spring.datasource.test2.minPoolSize = 3
spring.datasource.test2.maxPoolSize = 25
spring.datasource.test2.maxLifetime = 20000
spring.datasource.test2.borrowConnectionTimeout = 30
spring.datasource.test2.loginTimeout = 30
spring.datasource.test2.maintenanceInterval = 60
spring.datasource.test2.maxIdleTime = 60

4、新建数据源实体类 DBConfig1、DBConfig2

@Data //lombok
@ConfigurationProperties(prefix = "spring.datasource.test1") //读取配置文件中以 spring.datasource.test1 开始的配置
public class DBConfig1 {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String testQuery;
}
@Data //lombok
@ConfigurationProperties(prefix = "spring.datasource.test2") //读取配置文件中以 spring.datasource.test2 开始的配置
public class DBConfig1 {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String testQuery;
}

注意:新建数据源实体类,需要在启动文件中使用 @EnableConfigurationProperties 注解加载配置类 

@EnableConfigurationProperties(value = {DBConfig1.class, DBConfig2.class})

5、创建业务用户实体类对象(lombok)

@Data
public class User {
    private Integer id;
    private String name;
    private Integer age;
}

6、mapper接口

package com.demo.datasource.mapper1;

import com.demo.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper1 {
    @Insert("insert into t_user(name,age) values (#{name},#{age})")
    void save(User user);
}

 

package com.demo.datasource.mapper2;

import com.demo.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper2 {
    @Insert("insert into t_user(name,age) values (#{name},#{age})")
    void save(User user);
}

7、新建数据源配置文件 DataSourceConfig01、DataSourceConfig02

@Configuration
@MapperScan(basePackages = "com.demo.datasource.mapper1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSourceConfig01 {
    // 配置数据源
    @Primary
    @Bean(name = "test1DataSource")
    public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(testConfig.getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(testConfig.getPassword());
        mysqlXaDataSource.setUser(testConfig.getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("test1DataSource");

        xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
        xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
        xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
        xaDataSource.setTestQuery(testConfig.getTestQuery());
        return xaDataSource;
    }

    @Primary
    @Bean(name = "test1SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    @Primary
    @Bean(name = "test1SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

 

@Configuration
@MapperScan(basePackages = "com.demo.datasource.mapper2", sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class DataSourceConfig02 {
    // 配置数据源
    @Bean(name = "test2DataSource")
    public DataSource testDataSource(DBConfig2 testConfig) throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(testConfig.getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(testConfig.getPassword());
        mysqlXaDataSource.setUser(testConfig.getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("test2DataSource");

        xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
        xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
        xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
        xaDataSource.setTestQuery(testConfig.getTestQuery());
        return xaDataSource;
    }

    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    @Bean(name = "test2SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

注意两个文件内容中配置的区别。

8、service

@Service
public class UserService {
    @Autowired
    private UserMapper1 userMapper1;
    @Autowired
    private UserMapper2 userMapper2;

    /**
     * 不对该方法指定事务管理
     * */
    public void saveUser1(){
        User user1 = new User();
        user1.setName("小明");
        user1.setAge(11);
        userMapper1.save(user1);

        User user2 = new User();
        user2.setName("小梅");
        user2.setAge(11);
        userMapper2.save(user2);
    }

    /**
     * 不对该方法指定事务管理,并抛出异常
     * */
    public void saveUser2(){
        User user1 = new User();
        user1.setName("小明");
        user1.setAge(11);
        userMapper1.save(user1);

        int i = 1/0;//抛出异常

        User user2 = new User();
        user2.setName("小梅");
        user2.setAge(11);
        userMapper2.save(user2);
    }

    /**
     * 开启事务,由于使用jta+atomikos解决分布式事务,所以此处不必再指定事务
     * */
    @Transactional
    public void saveUser3(){
        User user1 = new User();
        user1.setName("小明");
        user1.setAge(11);
        userMapper1.save(user1);

        int i = 1/0;//抛出异常

        User user2 = new User();
        user2.setName("小梅");
        user2.setAge(11);
        userMapper2.save(user2);
    }

9、controller

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/saveUser1")
    public String saveUser1(){
        userService.saveUser1();
        return "success";
    }

    @RequestMapping("/saveUser2")
    public String saveUser2(){
        userService.saveUser2();
        return "success";
    }

    @RequestMapping("/saveUser3")
    public String saveUser3(){
        userService.saveUser3();
        return "success";
    }
}

10.测试

执行:http://localhost:8080/saveUser1

结果:test01 insert user 成功   test02 insert user 成功 (正常未抛出异常)

 

执行:http://localhost:8080/saveUser2

结果:test01 insert user 成功    test02 insert user 失败 (事务失败)

 

执行:http://localhost:8080/saveUser3

结果:test01 insert user 失败    test01 insert user 失败 (事务生效)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于快速构建 Java 应用程序的框架。它可以与多种其他框架和组件进行整合,以实现更丰富的功能。在这里,我们将讨论如何使用 Spring Boot 整合 Druid、MyBatis、JTA 分布式事务以及多数据源,同时使用 AOP 注解实现动态切换。 首先,我们可以在 Spring Boot 中集成 Druid 数据源。Druid 是一个高性能的 JDBC 连接池,可以提供监控和统计功能。我们可以通过在 pom.xml 文件中添加相关的依赖,并在 application.properties 文件中配置数据源信息,来实现 Druid 的集成。 接下来,我们可以整合 MyBatis 框架,它是一种优秀的持久化解决方案。我们可以使用 MyBatis 来操作数据库,并将其与 Druid 数据源进行整合。为此,我们需要在 pom.xml 文件中添加 MyBatis 和 MyBatis-Spring 的依赖,并配置 MyBatis 的相关配置文件。 此外,我们还可以使用 JTA(Java Transaction API)实现分布式事务JTA 可以在分布式环境中协调多个参与者的事务操作。我们可以在 pom.xml 文件中添加 JTA 的依赖,并在 Spring Boot 的配置文件中配置 JTA 的相关属性,以实现分布式事务的支持。 在实现多数据源时,我们可以使用 Spring Boot 的 AbstractRoutingDataSource 来实现动态切换数据源。这个类可以根据当前线程或其他条件选择不同的数据源来进行数据操作。我们可以通过继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey() 方法来指定当前数据源的 key。然后,在配置文件中配置多个数据源,并将数据源注入到 AbstractRoutingDataSource 中,从而实现动态切换。 最后,我们可以使用 AOP(Aspect Oriented Programming)注解来实现动态切换。AOP 是一种编程范式,可以通过在代码中插入特定的切面(Aspect)来实现横切关注点的处理。我们可以在代码中使用注解来标记需要切换数据源的方法,然后使用 AOP 技术来拦截这些方法,并根据注解中指定的数据源信息来进行数据源的切换。 综上所述,通过整合 Druid、MyBatis、JTA 分布式事务以及多数据源,并使用 AOP 注解实现动态切换,我们可以在 Spring Boot 中实现强大而灵活的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值