动态数据源的实现,主要依赖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;
}
}