动态数据源的实现,主要依赖AbstractRoutingDataSource类,这个类提供了抽象方法protected abstract Object determineCurrentLookupKey();

这个方法返回一个key,根据这个key spring就能决定使用哪个数据源,所以我们要实现动态数据源,只要继承这个类,实现这个方法,就能动态切换数据源

大概的实现步骤

1、使用ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>();来保存当前使用的数据源key

2、继承AbstractRoutingDataSource类,实现determineCurrentLookupKey方法,然后返回一个数据源key,key从上面DATA_SOURCE_KEY 对象取

3、创建多数据源对象,数据源配置可以保存在配置文件或者数据库

4、从配置文件或者数据源加载数据源配置,然后创建多数据源对象

5、前台发起请求的时候,要先设置使用那个数据源对象,即要设置DATA_SOURCE_KEY 

 

下面是具体代码实现

1、保存当前数据源对象的上下文对象

package com.als.api.dataSource;

/**
 * @author xiangwei.li
 * @version 1.0.0
 * @date 2023/3/29
 */

public class DataSourceContextHolder {

    /**
     * 当前线程数据源KEY
     */
    private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>();

    /**
     * 获取数据源key
     *
     * @return
     */
    public static String getDataSourceKey() {
        return DATA_SOURCE_KEY.get();
    }

    /**
     * 设置数据源key
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        DATA_SOURCE_KEY.set(key);
    }

    /**
     * 移除数据源
     */
    public static void remove() {
        DATA_SOURCE_KEY.remove();
    }
}

2、动态数据源切换类,重写了getConnect()方法,我们在每次调数据源之前,都会默认调这个方法获取连接,这个方法里面我们会指定我们要使用的数据源

package com.als.api.dataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author xiangwei.li
 * @version 1.0.0
 * @date 2023/3/29
 */
public class DynamicDataSource extends AbstractRoutingDataSource {


    /**
     * 切换数据源
     *
     * @return
     */
    @Override
    protected DataSource determineTargetDataSource() {
        Object dataSourceKey = this.determineCurrentLookupKey();
        // 默认系统数据源
        if (dataSourceKey == null) {
            return super.getResolvedDefaultDataSource();
        }
        DataSource dataSource = DataSourceManager.DATA_SOURCE_POOL_JDBC.get(dataSourceKey);

        if (dataSource == null) {
            throw new RuntimeException("数据源不存在!");
        }
        return dataSource;
    }

    /**
     * 获取连接
     *
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection() throws SQLException {
        Connection connection = this.determineTargetDataSource().getConnection();
        return connection;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}

3、创建数据源,新建一个类,这个类提供一个创建数据的方法,我们可以从配置文件或者数据库动态的创建数据源,然后把数据源保存到一个map对象里面,这样我们就可以根据key来切换数据源了

同时提供一个设置当前数据源的方法,调此方法就可以设置当前使用什么数据源

package com.als.api.dataSource;

import com.alibaba.druid.util.JdbcUtils;
import com.als.api.entity.ApiDataSourceEntity;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author xiangwei.li
 * @version 1.0.0
 * @date 2023/3/29
 */
@Component
public class DataSourceManager {

    /**
     * JDBC数据源连接池
     */
    public static final Map<String, DataSource> DATA_SOURCE_POOL_JDBC = new ConcurrentHashMap<>();

    public void createDataSource(ApiDataSourceEntity ds) {

        String datasourceId = ds.getDatasourceId();
        String username = ds.getUsername();
        String password = ds.getPassword();
        String jdbcUrl = ds.getJdbcUrl();

        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setMinimumIdle(5);
        dataSource.setMaxLifetime(1800000);
        dataSource.setIdleTimeout(600000);
        dataSource.setConnectionTimeout(10000);

        DataSourceManager.DATA_SOURCE_POOL_JDBC.put(datasourceId, dataSource);
    }

    /**
     * 设置数据源key
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        DataSourceContextHolder.setDataSourceKey(key);
    }
}

4、加载数据源

package com.als.api.dataSource;


import com.als.api.entity.ApiDataSourceEntity;
import com.als.api.service.ApiDataSourceService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * 启动初始化数据
 *
 * @author xiangwei.li
 */
@Component
public class LoadingDataSource implements InitializingBean {

    @Autowired
    private ApiDataSourceService apiDataSourceService;

    @Resource
    private DataSourceManager dataSourceManager;

    @Override
    public void afterPropertiesSet() {
        // 加载数据源
        List<ApiDataSourceEntity> baseDataSources = apiDataSourceService.list();
        for (ApiDataSourceEntity dataSource : baseDataSources) {
            try {
                dataSourceManager.createDataSource(dataSource);
            } catch (Exception e) {

            }
        }
    }
}

6、配置默认数据源和动态数据源

package com.als.api.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.als.api.dataSource.DynamicDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

/**
 * 数据源路由配置
 *
 * @author xiangwei.li
 */
@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.url}")
    private String jdbcUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    /**
     * 默认系统数据源
     *
     * @return
     */
    @Bean
    public DynamicDataSource defaultDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(jdbcUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setInitialSize(2);
        dataSource.setMinIdle(2);
        dataSource.setMaxActive(10);
        // 配置获取连接等待超时的时间
        dataSource.setMaxWait(6000);
        dataSource.setKeepAlive(true);
        //  配置一个连接在池中最小生存的时间,单位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(600000);
        dataSource.setMaxEvictableIdleTimeMillis(900000);
        // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(10000);
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        DynamicDataSource dynamic = new DynamicDataSource();
        dynamic.setTargetDataSources(new HashMap<>());
        // 设置默认数据源
        dynamic.setDefaultTargetDataSource(dataSource);
        return dynamic;
    }


}