实现功能:通过访问同一个接口携带不同参数查询不同的数据库(动态进行数据源切换)
1 创建一个数据源对象,管理数据源和过期时间
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DataSourceDetails {
/**
* 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。
*/
public static long idlePeriodTime = 10 * 60 * 1000;
/**
* 数据源
*/
private HikariDataSource dataSource;
/**
* 上一次使用的时间
*/
private long lastUseTime;
public DataSourceDetails(HikariDataSource dataSource) {
this.dataSource = dataSource;
this.lastUseTime = System.currentTimeMillis();
}
/**
* 刷新过期时间
*/
public void refreshTime(){
lastUseTime = System.currentTimeMillis();
}
/**
* 检查是否过期,如果过期关闭数据源
* @return
*/
public boolean check(){
if( System.currentTimeMillis()-lastUseTime > idlePeriodTime){
dataSource.close();
return true;
}
return false;
}
/**
* 获取数据源
*/
public HikariDataSource getDataSource() {
this.refreshTime();
return dataSource;
}
}
2.创建DataSourceHolder管理所有的数据源
public class DataSourceHolder {
/**
* 理动态数据源列表
*/
private Map<Long, DataSourceDetails> dataSourceMap = new ConcurrentHashMap();
/**
* 添加数据源
*/
public DataSource addDataSource(Long datasourceId, HikariDataSource dataSource) {
DataSourceDetails dataSourceTimer = new DataSourceDetails(dataSource);
dataSourceMap.put(datasourceId, dataSourceTimer);
return dataSource;
}
/**
* 获取数据源
*/
public DataSource getDataSource(DataSourceBean datasource){
//如果数据源已创建 直接返回
if(dataSourceMap.containsKey(datasource.getId())){
DataSourceDetails dataSourceDetails = dataSourceMap.get(datasource.getId());
return dataSourceDetails.getDataSource();
}
//数据源不存在,创建
return createDatasource(datasource);
}
/**
* 创建数据源
*/
public DataSource createDatasource(DataSourceBean datasource){
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(datasource.getUrl());
hikariDataSource.setUsername(datasource.getUsername());
hikariDataSource.setPassword(datasource.getPassword());
hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariDataSource.setConnectionTimeout(500);
return addDataSource(datasource.getId(),hikariDataSource);
}
/**
* 单例
*/
public static DataSourceHolder instance() {
return DDSHolderBuilder.instance;
}
private static class DDSHolderBuilder {
private static DataSourceHolder instance = new DataSourceHolder();
}
}
3.继承 HikariDataSource 实现我们自己的数据源
public class DynamicDataSource extends HikariDataSource {
@Override
public Connection getConnection() throws SQLException {
DataSource dataSource = DataSourceHolder.instance().getDataSource(dataSourceBeanThreadLocal.get());
return dataSource.getConnection();
}
}
-
配置mybatisPlus 使用我们自己的数据源
@MapperScan(basePackages = "org.gjw.dynamic.mapper",sqlSessionFactoryRef = "dynamicSqlSessionFactory",sqlSessionTemplateRef = "dynamicSqlSessionTemplate")
@Configuration
public class DynamicMybatisPlusConfiguration {
@Bean
public DataSource dynamicDataSource(){
//创建自己的数据源
return new DynamicDataSource();
}
@Bean("dynamicSqlSessionFactory")
public SqlSessionFactory dynamicSqlSessionFactoryBean( @Autowired DataSource dynamicDataSource) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
//开启日志
mybatisConfiguration.setLogImpl(StdOutImpl.class);
//下划线转驼峰
mybatisConfiguration.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
//设置别名包
sqlSessionFactoryBean.setTypeAliasesPackage("org.gjw.bean");
//设置mapper文件存放位置
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:dynamicMapper/**/*.xml");
sqlSessionFactoryBean.setMapperLocations(resources);
sqlSessionFactoryBean.setDataSource( dynamicDataSource );
return sqlSessionFactoryBean.getObject();
}
@Bean("dynamicSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate( @Qualifier("dynamicSqlSessionFactory") @Autowired SqlSessionFactory dynamicSqlSessionFactory){
return new SqlSessionTemplate( dynamicSqlSessionFactory );
}
@Bean("dynamicTransactionManager")
public TransactionManager transactionManager( @Autowired DataSource dynamicDataSource ){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource( dynamicDataSource );
return dataSourceTransactionManager;
}
}
5.创建拦截器获取接口的数据源id,然后通过数据源id判断查询哪个库
@Component
public class DynamicDataSourceInterceptor implements HandlerInterceptor {
public static ThreadLocal<DataSourceBean> dataSourceBeanThreadLocal = new ThreadLocal();
@Resource
private DataSourceMapper dataSourceMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String dataSourceId = request.getParameter("dataSourceId");
if(StrUtil.isBlank(dataSourceId)){
DataSourceBean dataSourceBean = new DataSourceBean(-1L, "jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=Hongkong&characterEncoding=utf-8&autoReconnect=true", "root", "root");
dataSourceBeanThreadLocal.set(dataSourceBean);
return true;
}
//这里可以进行权限校验等操作
//查询数据库判断获取对应的数据源id
DataSourceBean dataSourceBean = dataSourceMapper.selectById(dataSourceId);
dataSourceBeanThreadLocal.set(dataSourceBean);
return true;
}
}
配置拦截器
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Autowired
DynamicDataSourceInterceptor dynamicDataSourceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor( dynamicDataSourceInterceptor ).addPathPatterns("/**");
}
}
代码已放到Gitee : https://gitee.com/GJW520/dynamic-datasource.git