spring boot + mybatis plus + + multiple data sources distributed transaction

This article describes using a spring boot + mybatis plus + + implementation of multiple data sources distributed transaction.

As it involves multiple databases, naturally, involved in a distributed transaction. First understand a few concepts:

1, XA What is that?

XA distributed transactions is the norm proposed by the X / Open organization. The main XA specification defines the interface between the (global) transaction manager (Transaction Manager) and the (local) Resource Manager (Resource Manager).

2, Java Transaction API (Java Transaction API, referred JTA) is a Java Enterprise Edition application programming interfaces in a Java environment, allowing the completion of distributed transactions across multiple XA resources.

3, Atomikos is a value-added service for the Java platform and open source class transaction manager.

By importing

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

Distributed transactions can be realized

Talk is cheap, I will show you the code. Start on the Code

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>multi-datasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>multi-datasource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--分布式事务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </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>

    <build>
        <finalName>multi-datasource</finalName>
        <resources>
            <!--mapper.xml打包-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <!--resource资源文件打包-->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

spring:
  jta:
    # 事务管理器唯一标识符
    transaction-manager-id: txManager
  datasource:
    # Druid连接池配置。spring-boot-2默认连接池hikari不支持MysqlXADataSource
    type: com.alibaba.druid.pool.xa.DruidXADataSource
    # 最小空闲连接
    min-pool-size: 5
    # 池中最大连接数
    max-pool-size: 20
    # 设置连接在池中被自动销毁之前保留的最大秒数。 可选,默认为0(无限制)。
    max-life-time: 60
    # 返回连接前用于测试连接的SQL查询
    test-query: SELECT 1

    # 多数据源配置
    cpq-db:
      name: cpq
      url: jdbc:mysql://localhost:3306/cpq?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false
      username: root
      password: cpq..123
    shiro-db:
      name: shiro
      url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false
      username: root
      password: cpq..123

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #打印sql

Configuration data source DataSource, SqlSessionFactory

package com.example.multidatasource.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;

/**
 * cpq数据库配置类
 */
@Configuration
@MapperScan(basePackages = "com.example.multidatasource.cpq.**.mapper",
        sqlSessionFactoryRef = CpqDataSourcesConfig.SQL_SESSION_FACTORY)
public class CpqDataSourcesConfig {

    public static final String DATABASE_PREFIX = "spring.datasource.cpq-db.";

    public static final String DATA_SOURCE_NAME = "cpqDataSource";
    public static final String SQL_SESSION_FACTORY = "cpqSqlSessionFactory";


    /**
     * 通过配置文件创建DataSource,一个数据库对应一个DataSource
     * @param environment 环境变量,spring-boot会自动将IOC中的environment实例设置给本参数值
     * 由于IOC中有多个DataSource实例,必须给其中一个实例加上@Primary
     */
    @Primary
    @Bean(DATA_SOURCE_NAME)
    public DataSource dataSource(Environment environment) {
        return DataSourceUtil.createAtomikosDataSourceBean(DATA_SOURCE_NAME, environment, DATABASE_PREFIX);
    }

    /**
     * 通过dataSource创建SqlSessionFactory
     * 由于IOC中有多个DataSource实例,必须给其中一个实例加上@Primary
     */
    @Primary
    @Bean(name = SQL_SESSION_FACTORY)
    public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME) DataSource dataSource) throws Exception {
        return DataSourceUtil.createSqlSessionFactory(dataSource);
    }

}
package com.example.multidatasource.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;

/**
 * shiro数据库配置类
 */
@Configuration
@MapperScan(basePackages = "com.example.multidatasource.shiro.**.mapper",
        sqlSessionFactoryRef = ShiroDataSourcesConfig.SQL_SESSION_FACTORY)
public class ShiroDataSourcesConfig {

    public static final String DATABASE_PREFIX = "spring.datasource.shiro-db.";
    public static final String DATA_SOURCE_NAME = "shiroDataSource";
    public static final String SQL_SESSION_FACTORY = "shiroSqlSessionFactory";

    @Bean(DATA_SOURCE_NAME)
    public DataSource dataSource(Environment environment) {
        return DataSourceUtil.createAtomikosDataSourceBean(DATA_SOURCE_NAME, environment, DATABASE_PREFIX);
    }

    @Bean(name = SQL_SESSION_FACTORY)
    public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME) DataSource dataSource) throws Exception {
        return DataSourceUtil.createSqlSessionFactory(dataSource);
    }
}
package com.example.multidatasource.config;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;

public class DataSourceUtil {

    public static final String DATA_SOURCE_PREFIX = "spring.datasource.";

    /**
     * 创建AtomikosDataSourceBean是使用Atomikos连接池的首选类
     */
    public static AtomikosDataSourceBean createAtomikosDataSourceBean(String uniqueResourceName, Environment environment, String dataBase ){
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        // 这些设置大家可以进入源码中看java-doc
        // 数据源唯一标识
        atomikosDataSourceBean.setUniqueResourceName(uniqueResourceName);
        // XADataSource实现类,使用DruidXADataSource
        atomikosDataSourceBean.setXaDataSourceClassName(environment.getProperty(DATA_SOURCE_PREFIX+"type"));
        // 最小连接数,默认1
        atomikosDataSourceBean.setMinPoolSize(environment.getProperty(DATA_SOURCE_PREFIX+"min-pool-size", Integer.class));
        // 最大连接数,默认1
        atomikosDataSourceBean.setMaxPoolSize(environment.getProperty(DATA_SOURCE_PREFIX+"max-pool-size", Integer.class));
        // 设置连接在池中被自动销毁之前保留的最大秒数。 可选,默认为0(无限制)。
        atomikosDataSourceBean.setMaxLifetime(environment.getProperty(DATA_SOURCE_PREFIX+"max-life-time", Integer.class));
        // 返回连接前用于测试连接的SQL查询
        atomikosDataSourceBean.setTestQuery(environment.getProperty(DATA_SOURCE_PREFIX+"test-query"));

        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setDatabaseName(environment.getProperty(dataBase+"name"));
        mysqlXADataSource.setURL(environment.getProperty(dataBase+"url"));
        mysqlXADataSource.setUser(environment.getProperty(dataBase+"username"));
        mysqlXADataSource.setPassword(environment.getProperty(dataBase+"password"));
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);

        return atomikosDataSourceBean;
    }

    /**
     * 创建SqlSessionFactory实例
     */
    public static SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception{
        /**
         * 必须使用MybatisSqlSessionFactoryBean,
         * 不能使用SqlSessionFactoryBean,不然会报invalid bound statement (not found)
         *
         * com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory(javax.sql.DataSource)
         * 源码中也是使用MybatisSqlSessionFactoryBean
         * 并且源码中使用了@ConditionalOnMissingBean,即IOC中如果存在了SqlSessionFactory实例,mybatis-plus就不创建SqlSessionFactory实例了
         */
        MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        MybatisConfiguration configuration = new MybatisConfiguration();
        sessionFactoryBean.setConfiguration(configuration);
        return sessionFactoryBean.getObject();
    }

}

Create a distributed transaction service test

package com.example.multidatasource;

import com.example.multidatasource.cpq.girl.entity.Girl;
import com.example.multidatasource.cpq.girl.service.GirlService;
import com.example.multidatasource.shiro.role.entity.Role;
import com.example.multidatasource.shiro.role.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class CommonService {

    @Autowired
    GirlService girlService;
    @Autowired
    RoleService roleService;

    @Transactional(rollbackFor = Exception.class)
    public boolean save(String msg){
        Girl girl = new Girl();
        girl.setName("name "+msg);
        boolean b1 = girlService.save(girl);
        Role role = new Role();
        role.setRoleName("role-name"+msg);
        boolean b2 = roleService.save(role);
        //
        //if (b2){
        //    throw new RuntimeException("RuntimeException");
        //}

        return b1 && b2;
    }

}

Project complete code address here: https://github.com/CodingSoldier/java-learn/tree/master/project/mysql-learn/multi-datasource

 

Finally, to verify through the lower connection pool configuration is in effect. By SHOW PROCESSLIST; you can see the number of connections to the database, the results prove the connection pool set in force.


 

 

 

Published 51 original articles · won praise 14 · views 40000 +

Guess you like

Origin blog.csdn.net/u010606397/article/details/105134188
Recommended