spring boot + mybatis plus + 多数据源 + 分布式事务

本文介绍一种使用 spring boot + mybatis plus + 多数据源 + 分布式事务 的实现方式。

由于涉及多个数据库,自然就涉及到分布式事务。先了解几个概念:

1、XA是什么?

XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。

2、Java事务API(Java Transaction API,简称JTA ) 是一个Java企业版 的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务。

3、Atomikos 是一个为Java平台提供增值服务的并且开源类事务管理器。

通过导入


    org.springframework.boot
    spring-boot-starter-jta-atomikos

即可实现分布式事务

Talk is cheap, I will show you the code。开始上代码

pom.xml



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.5.RELEASE
         
    
    com.example
    multi-datasource
    0.0.1-SNAPSHOT
    multi-datasource
    Demo project for Spring Boot

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.projectlombok
            lombok
            true
        

        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.0
        
        
            mysql
            mysql-connector-java
            5.1.47
        
        
        
            org.springframework.boot
            spring-boot-starter-jta-atomikos
        
        
        
            com.alibaba
            druid
            1.1.12
        
        
        
            com.baomidou
            mybatis-plus-generator
            3.3.0
        
        
            org.apache.velocity
            velocity-engine-core
            2.2
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        multi-datasource
        
            
            
                src/main/java
                
                    **/*.xml
                
                false
            
            
            
                src/main/resources
                
                    **/*.*
                
            
        
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

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

配置数据源的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();
    }

}

新建一个service测试分布式事务

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;
    }

}

工程完整代码地址在此:https://github.com/CodingSoldier/java-learn/tree/master/project/mysql-learn/multi-datasource

 

最后来验证过下连接池的配置是否生效。通过  SHOW PROCESSLIST;   可以查看数据库的连接数量,运行结果证明连接池的设置生效的。

spring boot + mybatis plus + 多数据源 + 分布式事务_第1张图片
 

 

 

你可能感兴趣的:(mysql)