鹿鸣小站

鹿鸣小站

多数据源 - 不同Mapper域配不同数据源

1311
2022-10-11

前言

    有些时候一个项目里需要配备多个数据源,做主从,做读写,或者分离不同业务库等等,实现的方式也有多种,一般都有注解切换数据源,或者在代码行里手动切换数据源上下文环境,这两种方式可以说殊途同归,都是想办法把当前数据源设置到ThreadLocal里,再执行数据库操作的时候执行线程从ThreadLocal中取数据源。

    此篇文章的需求来源亦是分离不同的业务库,但与上述做法稍微有点不一样,本文没有做注解以提供在业务代码里灵活切换,而是项目初始化时就配置好各个mapper作用域的数据源,不同的mapper域调不同的数据源,互相之间互不干扰也无法切换,因此,在写业务代码时也可以灵活切换数据源,只是在写mapper操作数据库的时候要注意,各就各位,不要乱串SQL和数据源的对应关系。

思路

    基本思路是,有多少个数据库即配多少个数据源,每个数据源去扫描各自mapper包,即限制不同数据源作用的mapper域不一样。
    在写SQL的时候,是哪个库的表就要写到对应的mapper域,否则就是抛出表不存在异常。此外,多数据源之间的事务也是分布式事务,这意味着需要引入一套分布式事务处理方案,如果你的项目对事务要求严格,但此文没有对分布式事务的介绍。

配置

这是我的数据库配置参数,配在datasource.properties文件中

# 连接池
hikari.maximum-pool-size=20
hikari.minimum-idle=0
hikari.connection-timeout=60000
hikari.idle-timeout=600000
hikari.auto-commit=true

# 用户库
user.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
user.datasource.url=jdbc:mysql://192.168.73.60:3306/multiple_db_1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
user.datasource.username=root
user.datasource.password=root
user.datasource.pool-name=hikari-user

# 订单库
order.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
order.datasource.url=jdbc:mysql://192.168.73.60:3306/multiple_db_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
order.datasource.username=root
order.datasource.password=root
order.datasource.pool-name=hikari-order

# 支付库
pay.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
pay.datasource.url=jdbc:mysql://192.168.73.60:3306/multiple_db_3?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
pay.datasource.username=root
pay.datasource.password=root
pay.datasource.pool-name=hikari-pay

# 物流库
logistics.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
logistics.datasource.url=jdbc:mysql://192.168.73.60:3306/multiple_db_4?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
logistics.datasource.username=root
logistics.datasource.password=root
logistics.datasource.pool-name=hikari-logistics

初始化配置 - 包结构和配置文件说明

HikariCPProperties.java
- 连接池配置参数
LogisticsDBProperties.java
- 物流库配置参数
LogisticsDataSourceConfig.java
- 物流数据源配置
OrderDBProperties.java
- 订单库配置参数
OrderDataSourceConfig.java
- 订单数据源配置
PayDBProperties.java
- 支付库配置参数
PayDataSourceConfig.java
- 支付数据源配置
UserDBProperties.java
- 用户库配置参数
UserDataSourceConfig.java
- 用户数据源配置(默认数据源)

其中,类名以Properties结尾的类都是简单读上述配置文件中相关模块的内容,如HikariCPProperties读取连接池配置

package online.heycm.multipledb.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * hikari连接池参数
 */
@Component
@PropertySource("classpath:datasource.properties")
@Data
public class HikariCPProperties {

    @Value("${hikari.maximum-pool-size}")
    private int maximumPoolSize;

    @Value("${hikari.minimum-idle}")
    private int minimumIdle;

    @Value("${hikari.connection-timeout}")
    private long connectionTimeout;

    @Value("${hikari.idle-timeout}")
    private long idleTimeout;

    @Value("${hikari.auto-commit}")
    private Boolean autoCommit;
}

类名以DataSourceConfig结尾的类,即各个数据源的初始化配置了,以UserDataSourceConfig为例

package online.heycm.multipledb.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
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.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * 用户模块数据源(默认)
 */
@Slf4j
@Configuration
@MapperScan(basePackages = {UserDataSourceConfig.MAPPER_PACKAGES}, sqlSessionFactoryRef = UserDataSourceConfig.SQL_SESSION_FACTORY)
public class UserDataSourceConfig {

    static final String MAPPER_PACKAGES = "online.heycm.multipledb.domain.user.mapper";
    static final String MAPPER_LOCATIONS = "classpath:/mapper/user/*.xml";
    static final String DATA_SOURCE = "userDataSource";
    static final String TRANSACTION_MANAGER = "userTransactionManager";
    static final String SQL_SESSION_FACTORY = "userSqlSessionFactory";

    @Autowired
    private HikariCPProperties hikariCPProperties;
    @Autowired
    private UserDBProperties dbProperties;

    @Bean(DATA_SOURCE)
    @Primary
    public DataSource dataSource() {
        log.info("init DataSource: [{}]...", DATA_SOURCE);
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName(dbProperties.getDriverClassName());
        ds.setJdbcUrl(dbProperties.getUrl());
        ds.setUsername(dbProperties.getUsername());
        ds.setPassword(dbProperties.getPassword());
        ds.setPoolName(dbProperties.getPoolName());
        ds.setMaximumPoolSize(hikariCPProperties.getMaximumPoolSize());
        ds.setMinimumIdle(hikariCPProperties.getMinimumIdle());
        ds.setConnectionTimeout(hikariCPProperties.getConnectionTimeout());
        ds.setIdleTimeout(hikariCPProperties.getIdleTimeout());
        ds.setAutoCommit(hikariCPProperties.getAutoCommit());
        log.info("init DataSource: [{}] success.", DATA_SOURCE);
        return ds;
    }

    @Bean(TRANSACTION_MANAGER)
    @Primary
    public DataSourceTransactionManager transactionManager(@Qualifier(DATA_SOURCE) DataSource ds) {
        log.info("init TransactionManager: [{}] success.", TRANSACTION_MANAGER);
        return new DataSourceTransactionManager(ds);
    }

    @Bean(SQL_SESSION_FACTORY)
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE) DataSource ds) throws Exception {
        log.info("init SqlSessionFactory: [{}]...", SQL_SESSION_FACTORY);
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATIONS));
        // mybatis配置
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setLogImpl(StdOutImpl.class);
        factoryBean.setConfiguration(configuration);
        log.info("init SqlSessionFactory: [{}] success.", SQL_SESSION_FACTORY);
        return factoryBean.getObject();
    }

}

MAPPER_PACKAGES - 指定扫描的mapper包,即为此数据源的作用域,在这个mapper域下的所有数据库操作全部指向此数据源
MAPPER_LOCATIONS - 指定扫描 MAPPER_PACKAGES 对应的XML文件
DATA_SOURCE - 数据源实例名称
TRANSACTION_MANAGER - 事务管理器实例名称
SQL_SESSION_FACTORY - Session工厂实例名称


每个数据源都有配置:
数据源实例 - DataSource
事务管理器实例 - DataSourceTransactionManager
Session工厂实例 - SqlSessionFactory

此外,SpringBoot 需要一个数据源作为默认数据源,这里指定用户数据源为默认数据源,因此需要特别指定 @Primary,其他数据源配置大同小异,只需要扫描不同包下的mapper和mapper xml即可。



至此,不同mapper域的数据源配置就已经配好,可以在service的任何位置调用任意mapper,mapper根据配置访问各自数据源,再啰嗦一句,这里是无法统一协调各数据源的事务管理的,如果对事务要求较高,需要另行处理。


仓库地址

码云

https://gitee.com/he-ycm/multiple-db