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 失败 (事务生效)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值