动态数据源切换

实际业务需求中,往往可能有一些业务需求需要连接多个库,这时就需要一个项目配置多个数据库的情况,本文讲解实现数据源的动态切换的其中一种方式。
1、就是将多个数据源全部注入到bean中,根据需要实现多数据源之间的切换。
2、使用baomidou的@DS注解。见文章@DS注解实现数据源动态切换

  1. 配置文件配置多个数据库连接
#主库
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:postgresql://localhost:5432/sgeoc_sec_system
spring.datasource.username=postgres
spring.datasource.password=123456
spring.datasource.driver-class-name=org.postgresql.Driver

#从库
spring.slave-datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.slave-datasource.url=jdbc:postgresql://localhost:5432/server_test_system
spring.slave-datasource.username=postgres
spring.slave-datasource.password=123456
spring.slave-datasource.driver-class-name=org.postgresql.Driver
  1. 注入数据源
    将配置的数据源全部注入到bean中,可以设置默认的数据源,也可以动态设置系统使用的数据源。
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源注入
 *
 * @author yjj
 * @version 1.0
 * @since 2022 -08-29 09:50:30
 */
@Slf4j
@Configuration
public class DataSourceConfig {
    /**
     * 主库数据源bean名称
     */
    public static final String MASTER_DATASOURCE = "masterDataSource";
    /**
     * 从库数据源bean名称
     */
    public static final String SLAVE_DATASOURCE = "slaveDataSource";

    /**
     * 主库数据源对象bean生成
     * @param properties        配置项
     * @return                  DruidDataSource
     */
    @Bean(MASTER_DATASOURCE)
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource masterDataSource(DataSourceProperties properties) {
        DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
        log.info("配置主数据库:{}", build);
        return build;
    }

    /**
     * 从库数据源对象bean生成
     * @param properties        配置项
     * @return                  DruidDataSource
     */
    @Bean(SLAVE_DATASOURCE)
    @ConfigurationProperties(prefix = "spring.slave-datasource")
    public DruidDataSource slaveDataSource(DataSourceProperties properties) {
        DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
        log.info("配置从数据库:{}", build);
        return build;
    }

    /**
     * 数据源配置
     * @param masterDataSource      主库数据源对象
     * @param slaveDataSource       从库数据源对象
     * @return                      DataSource
     * @Primary                     优先使用这个DataSource对象bean
     */
    @Bean
    @Primary
    @DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})
    public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,
                                        @Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {
        if (StringUtils.isBlank(slaveDataSource.getUrl())) {
            log.info("没有配置从数据库,默认使用主数据库");
            return masterDataSource;
        }
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceConfig.MASTER_DATASOURCE, masterDataSource);
        map.put(DataSourceConfig.SLAVE_DATASOURCE, slaveDataSource);
        DynamicDataSource routing = new DynamicDataSource();
        //设置动态数据源
        routing.setTargetDataSources(map);
        //设置默认数据源
        routing.setDefaultTargetDataSource(masterDataSource);
        log.info("主从数据库配置完成");
        return routing;
    }
}
  1. 实现数据源动态切换
    通过继承AbstractRoutingDataSource类,这个抽象类中有一个属性为【targetDataSources】,该属性为map结构,所有需要切换的数据源都存在这个map里面,根据指定的key获取对应的数据源,从而实现动态数据源的切换。则需要重写该抽象类中的【determineCurrentLookupKey】抽象方法,该方法的返回值决定了需要切换的数据源key,然后根据这个key去之前的【targetDataSources】Map中取数据源。
import com.southsmart.sso.util.DataSourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源
 *
 * @author yjj
 * @version 1.0
 * @since 2022 -08-29 09:55:26
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String db = DataSourceUtil.getDb();
        log.info("使用数据源:{}", db);
        return db;
    }
}
  1. 切换数据源管理类
/**
 * 数据源切换工具
 *
 * @author yjj
 * @version 1.0
 * @since 2022 -08-29 09:54:05
 */
public class DataSourceUtil {
    /**
     *  数据源属于一个公共的资源
     *  采用ThreadLocal可以保证在多线程情况下线程隔离
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    /**
     * 设置数据源名
     *
     * @param dbType the db type
     */
    public static void setDb(String dbType) {
        contextHolder.set(dbType);
    }

    /**
     * 获取数据源名
     *
     * @return db
     */
    public static String getDb() {
        return (contextHolder.get());
    }

    /**
     * 清除数据源名
     */
    public static void clearDb() {
        contextHolder.remove();
    }
}
  1. 使用
/**
     * 默认使用主库
     * 通过setDB()方法可以切换你想使用的数据源
     */
    @PostMapping("loginTest")
    public Result<String> test() {
    	//切换使用从库数据源
        DataSourceUtil.setDb(DataSourceConfig.SLAVE_DATASOURCE);
        return Result.ok();
    }