SpringBoot之解决整合多数据源分布式事物问题

概念:

  上一章只是解决了单事物问题,也就是说同时只能使用自己的数据源,并指定事物管理,才能使用,那么如果同时使用多个数据源,就会产生分布式事物问题

  分布式事物问题分两种:

    一种是这种一个项目多个数据源的分布式事物问题

    还有一种就是多个项目多个数据源之间的分布式事物问题

  这一章就来解决一下第一种一个项目多个数据源的分布式事物问题

编写接口:

这样的话就只能指定一个事物管理器,并不能两个数据库的事物都控制到,如果中间出现错误就会一个事物成功,一个事物失败,造成所谓的分布式事物问题

那么如何解决呢?

  采用一个统一的事物管理器来同时管理多个数据源

解决方案:

采用jta-atomikos

添加Maven依赖

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

修改配置 文件也就是之前写的application-dts.yml

spring:
  datasource:
    ###源数据库
    springboot:
      jdbc-url: jdbc:mysql://192.168.0.23:3306/springboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
      username: root
      password: root
      borrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60
      maxLifetime: 20000
      maxPoolSize: 25
      minPoolSize: 3
      uniqueResourceName: springbootDatasource

    ###新数据库
    springbootdts:
      jdbc-url: jdbc:mysql://192.168.0.23:3306/springboot_dts?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
      username: root
      password: root
      borrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60
      maxLifetime: 20000
      maxPoolSize: 25
      minPoolSize: 3
      uniqueResourceName: springbootdtsDatasource

创建两个模型类用于存储两个配置

 SpringBootConfig

package com.springboot.demo.model;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.datasource.springboot")
@Data
public class SpringBootConfig {
    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;

    private String uniqueResourceName;
}

SpringBootDtsConfig

package com.springboot.demo.model;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.datasource.springbootdts")
@Data
public class SpringBootDtsConfig {

    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;

    private String uniqueResourceName;

}

修改启动类

 修改之前写的两个Mapper扫描配置类

 修改后的SpringBootDataSourceConfig.java

package com.springboot.demo.config;

import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.springboot.demo.model.SpringBootConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
@MapperScan(basePackages = "com.springboot.demo.springboot.mapper", sqlSessionFactoryRef = "springbootSqlSessionFactory")
public class SpringBootDataSourceConfig {

    /**
     * 将会员db注册到容器中
     *
     * @return
     */
    @Bean(name = "springbootDataSource")
//    @ConfigurationProperties(prefix = "spring.datasource.springboot")
    public DataSource springbootDataSource(SpringBootConfig springBootConfig) throws SQLException {
//        return DataSourceBuilder.create().build();
// 1.创建MysqlXADataSource
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(springBootConfig.getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(springBootConfig.getPassWord());
        mysqlXaDataSource.setUser(springBootConfig.getUserName());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        // 2.将本地事务注册到创 Atomikos全局事务
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName(springBootConfig.getUniqueResourceName());
        xaDataSource.setMinPoolSize(springBootConfig.getMinPoolSize());
        xaDataSource.setMaxPoolSize(springBootConfig.getMaxPoolSize());
        xaDataSource.setMaxLifetime(springBootConfig.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(springBootConfig.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(springBootConfig.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(springBootConfig.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(springBootConfig.getMaxIdleTime());
        xaDataSource.setTestQuery(springBootConfig.getTestQuery());
        return xaDataSource;
    }

    /**
     * 将会员SqlSessionFactory注册到容器中
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "springbootSqlSessionFactory")
    public SqlSessionFactory springbootSqlSessionFactory(@Qualifier("springbootDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 创建会员管理器
     *
     * @param dataSource
     * @return
     */
    /*@Bean(name = "springbootTransactionManager")
    public DataSourceTransactionManager springbootTransactionManager(@Qualifier("springbootDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }*/

    /**
     * 创建订单sqlSesion模版
     *
     * @param sqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean(name = "springbootSqlSessionTemplate")
    public SqlSessionTemplate springbootSqlSessionTemplate(
            @Qualifier("springbootSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


}

修改后的SpringBootDtsDataSourceConfig.java

package com.springboot.demo.config;

import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.springboot.demo.model.SpringBootDtsConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
@MapperScan(basePackages = "com.springboot.demo.springbootdts.mapper", sqlSessionFactoryRef = "springbootdtsSqlSessionFactory")
public class SpringBootDtsDataSourceConfig {
    /**
     * 将会员db注册到容器中
     *
     * @return
     */
    @Bean(name = "springbootdtsDataSource")
//    @ConfigurationProperties(prefix = "spring.datasource.springbootdts")
    public DataSource springbootdtsDataSource(SpringBootDtsConfig springBootDtsConfig) throws SQLException {
//        return DataSourceBuilder.create().build();
        // 1.创建MysqlXADataSource
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(springBootDtsConfig.getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(springBootDtsConfig.getPassWord());
        mysqlXaDataSource.setUser(springBootDtsConfig.getUserName());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        // 2.将本地事务注册到创 Atomikos全局事务
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName(springBootDtsConfig.getUniqueResourceName());
        xaDataSource.setMinPoolSize(springBootDtsConfig.getMinPoolSize());
        xaDataSource.setMaxPoolSize(springBootDtsConfig.getMaxPoolSize());
        xaDataSource.setMaxLifetime(springBootDtsConfig.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(springBootDtsConfig.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(springBootDtsConfig.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(springBootDtsConfig.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(springBootDtsConfig.getMaxIdleTime());
        xaDataSource.setTestQuery(springBootDtsConfig.getTestQuery());
        return xaDataSource;
    }

    /**
     * 将会员SqlSessionFactory注册到容器中
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "springbootdtsSqlSessionFactory")
    public SqlSessionFactory springbootdtsSqlSessionFactory(@Qualifier("springbootdtsDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 创建会员管理器
     *
     * @param dataSource
     * @return
     */
   /* @Bean(name = "springbootdtsTransactionManager")
    public DataSourceTransactionManager springbootdtsTransactionManager(@Qualifier("springbootdtsDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }*/

    /**
     * 创建订单sqlSesion模版
     *
     * @param sqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean(name = "springbootdtsSqlSessionTemplate")
    public SqlSessionTemplate springbootdtsSqlSessionTemplate(
            @Qualifier("springbootdtsSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

修改接口去除掉注解中指定的事物管理器

 启动项目测试:

  启动的时候出现了一个小问题,找了一会,发现是yml配置文件和模型没有对上,修改一下jdbc-url改成url,因为是我们自己装载数据源就不用设置为jdbc-url了

 再次启动,清空数据库,先测试正确的

访问报错:因为之前在user2接口上加了事物注解,并指定了事物管理器所以报错了,因为那个事物管理器已经不存在了,注释掉就可以

 再次启动测试

 测试成功..为什么是error呢,这是个失误因为我返回的就是error

 查看数据库

 

 成功了,接下来测试报错的

因为是写死的所以需要修改重启

 

 测试输入num为0

后端报错

 查看数据库数据

 

 并没有插入,到此单项目多数据源分布式事物问题解决,开心...

作者:彼岸舞

时间:2021\01\28

内容关于:SpringBoot

本文来源于网络,只做技术分享,一概不负任何责任 

Logo

鸿蒙生态一站式服务平台。

更多推荐