实现功能:通过访问同一个接口携带不同参数查询不同的数据库(动态进行数据源切换)
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