Multi-data source solution based on Mybatis and Mybatis-Plus

introduction

Recently, there is a project that needs to support multi-tenancy (a separate article will be written after multi-tenancy). Multi-tenant architecture needs to use multiple data sources, that is, physical isolation. Different tenants need to correspond to different RMDB database instances, so this article is first Discussion on multiple data sources.

Usually our project only has a unique data source and a corresponding set of database connection pools, such as the following configuration in the SpringBoot application:

# 基础配置
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/my_db?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    # Hikari 连接池配置
    hikari:
      # 最小空闲连接数量
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 180000
      # 连接池最大连接数,默认是10
      maximum-pool-size: 10
      # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      auto-commit: true
      # 连接池名称
      pool-name: MyHikariCP
      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      max-lifetime: 1800000
      # 数据库连接超时时间,默认30秒,即30000
      connection-timeout: 30000
      connection-test-query: SELECT 1

Taking the Mybatis ecosystem as an example, there are two ways to support multiple data sources as follows.

Method 1 - Using native Mybatis subcontracting method

This method needs to subcontract the Mapper interface and mapper.xml according to the data source. If
there are 2 data sources in the following figure, they need to be divided into 2 packages, such as ds1 and ds2:
insert image description here

At the same time, it is more important to inject different DataSources into the Mapper under different packages in Mybatis, so each data source needs to be configured separately. For example, there are two data sources in the screenshot corresponding to the two configuration classes DataSourceConfig1 and DataSourceConfig2. At the same time, you need Set a data source as the main data source to prevent Spring startup from reporting errors when it cannot inject data sources.

The multi-data source configuration application.yml is planned as follows:

spring:
  # DataSource Config
  datasource:
    ds1: # 数据源1
      # Hikari 连接池配置,具体配置属性同spring.datasource.hikari.*
      jdbc-url: jdbc:mysql://localhost:3306/multi-ds-1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource
      # 最小空闲连接数量
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 180000
      # 连接池最大连接数,默认是10
      maximum-pool-size: 10
      # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      auto-commit: true
      # 连接池名称
      pool-name: DS1-POOL
      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      max-lifetime: 1800000
      # 数据库连接超时时间,默认30秒,即30000
      connection-timeout: 30000
      connection-test-query: SELECT 1
    ds2: # 数据源2
      # Hikari 连接池配置,具体配置属性同spring.datasource.hikari.*
      jdbc-url: jdbc:mysql://localhost:3306/multi-ds-2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource
      # 最小空闲连接数量
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 180000
      # 连接池最大连接数,默认是10
      maximum-pool-size: 10
      # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      auto-commit: true
      # 连接池名称
      pool-name: DS2-POOL
      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      max-lifetime: 1800000
      # 数据库连接超时时间,默认30秒,即30000
      connection-timeout: 30000
      connection-test-query: SELECT 1

The multi-data source configuration class is defined as follows:

/**
 * 数据源1 - 配置
 *
 * 注:默认仅@Primary主数据源支持事务@Transactional
 *
 * @author luohq
 * @date 2022-08-06
 */
@Configuration
//注意此处需扫描对应数据源包下的mapper接口,且sqlSessionFactory为当前类中定义的SqlSessionFactory
@MapperScan(basePackageClasses = {
    
    MyDataMapper1.class}, sqlSessionFactoryRef = "ds1SqlSessionFactory")
public class DataSourceConfig1 {
    
    

    @Primary // 表示这个数据源是默认数据源, 这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)
    @Bean("ds1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.ds1") //读取application.yml中的配置参数映射成为一个对象
    public DataSource ds1DataSource1() {
    
    
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean("ds1SqlSessionFactory")
    public SqlSessionFactory ds1SqlSessionFactory(@Qualifier("ds1DataSource") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致)
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/ds1/*.xml"));
        return bean.getObject();
    }

    @Primary
    @Bean("ds1SqlSessionTemplate")
    public SqlSessionTemplate ds1SqlSessionTemplate(@Qualifier("ds1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

----------------------------------------------------------------------------

/**
 * 数据源2 - 配置
 * 注:默认仅@Primary主数据源支持事务@Transactional,当前非主数据源不支持事务
 * @author luohq
 * @date 2022-08-06
 */
@Configuration
//注意此处需扫描对应数据源包下的mapper接口,且sqlSessionFactory为当前类中定义的SqlSessionFactory
@MapperScan(basePackageClasses = {
    
    MyDataMapper2.class}, sqlSessionFactoryRef = "ds2SqlSessionFactory")
public class DataSourceConfig2 {
    
    

    @Bean("ds2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.ds2")
    public DataSource ds2DataSource(){
    
    
        return DataSourceBuilder.create().build();
    }

    @Bean("ds2SqlSessionFactory")
    public SqlSessionFactory ds2SqlSessionFactory(@Qualifier("ds2DataSource") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/ds2/*.xml"));
        return bean.getObject();
    }

    @Bean("ds2SqlSessionTemplate")
    public SqlSessionTemplate ds2SqlSessionTemplate(@Qualifier("ds2SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

When using multiple data sources, you only need to inject the Mapper interface under different data source packages, such as:

/**
 * <p>
 * 我的数据 服务实现类
 * </p>
 *
 * @author luohq
 * @since 2022-08-06
 */
@Service
public class MyDataServiceImpl implements IMyDataService {
    
    

    @Resource
    private MyDataMapper1 myDataMapper1;
    @Resource
    private MyDataMapper2 myDataMapper2;

    @Override
    public MyData findByIdFromDs1(Long id) {
    
    
        return this.myDataMapper1.selectById(id);
    }

    @Override
    public MyData findByIdFromDs2(Long id) {
    
    
        return this.myDataMapper2.selectById(id);
    }

    /**
     * 仅@Primary主数据源ds1支持事务,非主数据源ds2不支持事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addBothData(MyData myData1, MyData myData2) {
    
    
        Integer retCount1 = this.myDataMapper1.insert(myData1);
        Integer retCount2 = this.myDataMapper2.insert(myData2);
        if (true) {
    
    
            throw new RuntimeException("业务异常 - 制造数据库回滚!");
        }
        return retCount1 + retCount2;
    }

    /**
     * 仅@Primary主数据源支持事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addData1(MyData myData) {
    
    
        Integer retCount = this.myDataMapper1.insert(myData);
        return retCount;
    }

    /**
     * 非主数据源不支持事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addData2(MyData myData) {
    
    
        Integer retCount = this.myDataMapper2.insert(myData);
        return retCount;
    }
}

The above method can indeed realize multiple data sources, but this method has the following problems:

  • The transaction opened by @Transactional only supports the previous statement @Primary的主数据源, and does not support other非@Primary数据源
    • Call alone to @Primary数据源support transactions, see MyDataServiceImpl.addData1 in the code above
    • Calling alone 非@Primary数据源does not support transactions, see MyDataServiceImpl.addData2 in the above example code
    • Combining calls to multiple data sources only @Primary数据源supports transactions. For example, addBothData calls myDataMapper1 and myDataMapper2 at the same time in the above example code, and actually tests the operation of myDataMapper1 to support transactions, while myDataMapper2 is completely out of the management of the current transaction.
    • In summary, this kind of transaction control scenario is more suitable for the scenario of read-write separation (one master and one slave) , @Primary主数据源only for write operations , such as MyDataWriteMapper.java, and other 非@Primary数据源only for read operations , such as MyDataReadMapper.java.
  • If different data sources correspond to different DB data structures, or the aforementioned separation of reading and writing SQL, it is no problem to define different Mapper interfaces and Mapper.xml under different data source packages, but for similar multi-tenant scenarios, It is only to isolate the data storage location, but the data structure between different data sources is the same. At this time, it is obviously unreasonable to maintain multiple sets of the same Mapper interface and Mapper.xml under different data source packages.
    • In summary, the architecture of dividing multiple data sources through native Mybatis subcontracting is not suitable for multi-tenant architecture

For the source code of the above example, see:
https://gitee.com/luoex/multi-datasource-demo/tree/master/mb-package-multi-ds

Method 2 - Use Mybatis-Plus and the corresponding Dynamic-Datasource extension [recommended]

In actual development, most of my side directly use Mybatis-Plus as the DAO layer. As an enhancement of Mybatis, Mybatis-Plus provides many out-of-the-box convenient features, such as built-in CRUD operations and powerful Wrapper-based Conditional constructors, pagination, ID generation, and more. In the Mybatis-Plus ecosystem, the author also provides a multi-data source solution, which is based on the implementation of dynamic-datasource-spring-boot-starter :

<!-- Mybatis-Plus依赖 -->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.5.2</version>
</dependency>

<!-- Dynamic-DataSource多数据源依赖 -->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
	<version>3.5.1</version>
</dependency>

The dynamic-datasource extension code is open source:
https://github.com/baomidou/dynamic-datasource-spring-boot-starter
https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter
but the documentation It is paid:
https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611
The document I read was purchased by a small partner of the company. It can be disseminated within the company, but not on the Internet.

The integration of dynamic-datasource is relatively convenient, and supports many connection pools such as Druid and HikariCP.
Taking the integrated HikariCP connection pool as an example, the application.yml configuration is as follows:

# dynamic-datasource多数据源配置
spring:
  datasource:
    dynamic:
      primary: ds1 #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      hikari: # 全局hikariCP参数,所有值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
        connection-timeout: 30000
        max-pool-size: 10
        min-idle: 5
        idle-timeout: 180000
        max-lifetime: 1800000
        connection-test-query: SELECT 1
      datasource:
        ds1: # 数据源名称即对应连接池名称
          url: jdbc:mysql://localhost:3306/multi-ds-1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
          hikari: # 当前数据源HikariCP参数(继承全局、部分覆盖全局)
            max-pool-size: 20
        ds2:
          url: jdbc:mysql://localhost:3306/multi-ds-2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
          hikari:
            max-pool-size: 15

# Mybatis-Plus相关配置
mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id

The code structure is as shown in the figure below. Compared with the previously mentioned method of subcontracting based on native Mybatis, this method does not need to subcontract the Mapper interface and Mapper.xml: when switching data sources, you can pass the specified
insert image description here
method in the Service implementation class @DS("具体配置中的数据源名称")Corresponding data source:

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.luo.demo.multi.ds.dynamic.dto.MyDataQueryDto;
import com.luo.demo.multi.ds.dynamic.entity.MyData;
import com.luo.demo.multi.ds.dynamic.mapper.MyDataMapper;
import com.luo.demo.multi.ds.dynamic.service.IMyDataService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * <p>
 * 我的数据 服务实现类
 * </p>
 *
 * @author luohq
 * @since 2022-08-07
 */
@Service
public class MyDataServiceImpl implements IMyDataService {
    
    
    @Resource
    private MyDataMapper myDataMapper;

    @Override
    @DS("ds1")
    public MyData findByIdFromDs1(Long id) {
    
    
        //selectById - 支持自动拼接租户Id参数
        return this.myDataMapper.selectById(id);
    }

    @Override
    @DS("ds1")
    public MyData findByQueryFromDs1(MyDataQueryDto myDataQueryDto) {
    
    
        //QueryWrapper - 支持自动拼接租户Id参数
        return this.myDataMapper.selectOne(Wrappers.<MyData>lambdaQuery()
                .eq(Objects.nonNull(myDataQueryDto.getId()), MyData::getId, myDataQueryDto.getId())
                .like(StringUtils.hasText(myDataQueryDto.getMyName()), MyData::getMyName, myDataQueryDto.getMyName()));
    }

    @Override
    public MyData findByName(String myName) {
    
    
        //mapper.xml自定义查询 - 支持自动拼接租户Id参数
        return this.myDataMapper.selectByName(myName);
    }

    @Override
    @DS("ds2")
    public MyData findByIdFromDs2(Long id) {
    
    
        return this.myDataMapper.selectById(id);
    }

    /**
     * 单@Transactional内不支持切换数据源,
     * 即先使用ds1,则后续一直使用同一ds1连接,
     * 当前事务生效,但都会插入ds1中
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addBothData(MyData myData1, MyData myData2) {
    
    
        Integer retCount1 = this.addData1(myData1);
        Integer retCount2 = this.addData2(myData2);
        //if (true) {
    
    
        //    throw new RuntimeException("业务异常 - 制造数据库回滚!");
        //}
        return retCount1 + retCount2;
    }

    /**
     * 支持事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @DS("ds1")
    public Integer addData1(MyData myData) {
    
    
        //支持自动设置tenantId
        Integer retCount = this.myDataMapper.insert(myData);
        return retCount;
    }

    /**
     * 支持事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @DS("ds2")
    public Integer addData2(MyData myData) {
    
    
        //支持自动设置tenantId
        Integer retCount = this.myDataMapper.insert(myData);
        return retCount;
    }
}

Note about the @DS annotation:

  • @DS annotation is based on AOP implementation
  • @DS is recommended to be placed on the method of the Service implementation class, or it can be annotated on the Mapper interface (not on the Mapper method)
  • @DS+@Transactional supports transactions, but the @Transactional method does not support switching data sources , see MyDataServiceImpl.addBothData in the sample code above, that is, use ds1 first, then use the same ds1 connection all the time, the current transaction takes effect, but will be inserted into ds1 .
    • After the transaction is started, the spring transaction manager will ensure that the entire thread gets the same connection subsequently under the transaction.

For the source code of the above example, see:
https://gitee.com/luoex/multi-datasource-demo/tree/master/mp-dynamic-ds

2.1 @DSTransactional

dynamic-datasource provides custom @DSTransactional annotations,

  • Support local transactions between multiple data sources
  • The @DSTransactional method supports switching data sources , and the DS needs to be switched across service calls, otherwise only the first data source is used
  • The core principle of @DSTransactional is to proxy connection, and get a connection according to different databases and put it into ConnectionFactory. If the overall commit succeeds, the overall rollback fails.

The sample code for using @DSTransactional to support multi-data source local transactions is as follows:

/**
 * 本地多数据源事务 - 测试服务实现类<br/>
 *
 * @author luohq
 * @date 2022-08-09 13:42
 */
@Service
public class MyDataMultiDsLocalTxServiceImpl implements IMyDataMultiDsLocalTxService {
    
    

    @Resource
    private IMyDataService myDataService;

    /**
     * 此处需使用@DSTransactional,需注意不是Spring @Transactional,
     * 使用@DSTransactional支持切换数据源,而@Transactional方法中无法切换数据源
     * 注:需跨服务调用切换DS,否则仅使用第一个数据源,即2条记录都插入到ds1中
     */
    @Override
    @DSTransactional
    //@DS("ds1") //如果ds1是默认数据源则不需要DS注解。
    public Integer addBothData(MyData myData1, MyData myData2) {
    
    
        Integer retCount1 = this.myDataService.addData1(myData1);
        Integer retCount2 = this.myDataService.addData2(myData2);
        //if (true) {
    
    
        //    throw new RuntimeException("测试多数据源异常回滚!");
        //}
        return retCount1 + retCount2;
    }

}

------------------------------------------------------------------------------------

/**
 * <p>
 * 我的数据 服务实现类
 * </p>
 *
 * @author luohq
 * @since 2022-08-07
 */
@Service
public class MyDataServiceImpl extends ServiceImpl<MyDataMapper, MyData> implements IMyDataService {
    
    

    @Resource
    private MyDataMapper myDataMapper;

    /**
     * 支持事务 - @DSTransactional区别于Spring @Transactional
     */
    @Override
    @DSTransactional
    @DS("ds1")
    public Integer addData1(MyData myData) {
    
    
        //支持自动设置tenantId
        Integer retCount = this.myDataMapper.insert(myData);
        return retCount;
    }
    /**
     * 支持事务 - @DSTransactional区别于Spring @Transactional
     */
    @DS("ds2")
    @Override
    @DSTransactional
    public Integer addData2(MyData myData) {
    
    
        //支持自动设置tenantId
        Integer retCount = this.myDataMapper.insert(myData);
        return retCount;
    }
}

In the sample code, the MyDataMultiDsLocalTxServiceImpl main service, the addBothData method in the main service calls addData1 using @DS("ds1) and addData2 using @DS("ds2") in MyDataServiceImpl across services, that is, the main service uses the default data source (the main service The method can also specify the data source through @DS(”…”), and use the default if not specified), and call the services of different data sources. The
transaction annotations and final effects of each service method are summarized in the following table:

Main service
MyDataMultiDsLocalTxServiceImpl.addBothData
ds1 service
@DS("ds1") MyDataServiceImpl.addData1
ds2 service
@DS("ds1") MyDataServiceImpl.addData1
Effect
@DSTransactional @DSTransactional @DSTransactional Call the main service to support global transaction commit and rollback,
and call the ds service separately to support transactions
@DSTransactional @Transactional @Transactional Call the main service to support global transaction commit and rollback,
and call the ds service separately to support transactions
@DSTransactional none none Calling the main service supports global transaction commit and rollback, and
calling the ds service alone does not support transactions
none @DSTransactional @DSTransactional Does not support global transactions,
call the ds service to manage their own transactions
@Transactional Spring @Transactional does not support switching data sources

2.2 Multi-data source transaction extension

The @DSTransactional mentioned above supports multi-data source local transactions, how to define multi-data source local transactions ?
Refer to the following service distribution diagram:
insert image description here
the orange ServiceA|B|C is the service instance, and the blue Resource is the corresponding database storage instance of each service instance. The
entire service call chain forms a distributed transaction , and the service instances themselves Transaction management is a local transaction .
The green boxes in the figure below mark their respective local transactions . ServiceA and ServiceC only contain a unique data source, while ServiceB uses two data sources at the same time, that is, the transaction that ServiceB needs to manage is Local transactions for multiple data sources . I mentioned the @DSTransactional
insert image description here
annotation in Dynamic-Datasource before ,

  • Using this annotation alone can solve the scenario of multi-data source local transactions of a single application or a single microservice application that does not involve distributed transactions

And the complete distributed transaction (and there is a situation where a single service contains multiple data sources) scenario in the above figure can be combined with Seata:

  • Use Dynamic-Datasource @DSTransactional to solve local transactions, multi-data source local transaction management
  • Use Seata @GlobalTransaction to solve global distributed transaction management

Note:
Be cautious when introducing multiple data sources, especially in microservice scenarios, you can consider splitting multiple data sources into different individual services as early as possible.

  • After splitting, a single service only contains a unique data source, which reduces the difficulty of development and facilitates optimization for a single data source.
  • At this point, you can directly use Spring's native @Transactional to manage single data source local transactions (that is, without introducing dynamic-datasource extensions),
  • If there is a distributed transaction management scenario, Seata can be introduced again.

2.3 Multi-tenancy

Using Mybatis-Plus and the corresponding Dynamic-Datasource extension,

  • You can share a set of Mapper interface and Mapper.xml file,
  • And in addition to the above-mentioned support for switching data sources using @DS annotations,
  • It also supports programs to manually switch data sources at runtimeDynamicDataSourceContextHolder.push("ds1")
    • It also supports the built-in capability of dynamically parsing data sources based on @DS
      • @DS("#session.tenantName") - get from session
      • @DS("#header.tenantName") - Get it from the request header
      • @DS("#user.tenantName") - get from parameter using SPEL
  • Most importantly, it also supports dynamic addition and removal of data sources

Through the above feature description, it is not difficult to find that it fits well with the multi-tenant architecture:

  • A single tenant corresponds to a separate data source, and the corresponding data source can be obtained through the tenant ID (such as the corresponding request header TENANT-ID), such as
    • The tenant ID directly corresponds to the data source name
    • Or associate the corresponding data source name with the tenant ID
  • When different tenant users access the system, it supports dynamic switching of data sources according to the tenant ID
  • Tenants support addition or removal, and the data sources corresponding to tenants also support dynamic addition or removal in the application
    • Data source synchronization in distributed deployment needs to be considered

The above solution based on Mybatis-Plus and Dynamic-Datasource extension can implement a multi-tenant (physical isolation) architecture. The multi-tenant implementation solution will be introduced in a separate article later, and this article will not focus on it.


The above only introduces some functions of Mybatis-Plus and Dynamic-Datasource extensions. Interested partners can continue to study in depth:

  • Such as the realization of read-write separation based on data source grouping (one master and multiple slaves)
    • master, slave_1, slave_2
    • MasterSlaveAutoRoutingPlugin
  • custom implementation
  • Integrate P6spy, Quratz, ShardingJdbc, etc.
  • Integrate Seata

reference:

Mybatis subpackage:
springboot-integrate multiple data source configuration (MapperScan subpackage, mybatis plus - dynamic-datasource-spring-boot-starter)

Mybatis-Plus Dynamic Datasource:
https://baomidou.com/pages/a61e1b/
https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

Guess you like

Origin blog.csdn.net/luo15242208310/article/details/121330039