大家好,最近有个需求,就是一套系统,给不同公司使用,还要数据隔离,所以就设计了这种多租户模式,使用请求不同,访问的数据源也不同的多租户模式,下面让我们来一起看一下,是否对你有所启发...
1.首先说下设计思路
先默认连接一个数据库,数据库里面有一个数据源配置表,配置了所有租户的不同数据源,在启动项目的时候初始化连接这些数据源,放在一个集合中,然后不同租户的请求要在请求头设置一个参数,在请求的时候拦截这个参数,来控制访问的数据源,就是这么简单。
2.来具体看下
首先设置一套动态数据源保存地方
public class DynamicDataSource extends AbstractRoutingDataSource {
// 存储数据源
private Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 配置DataSource, defaultTargetDataSource为主数据库
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
Object dataSourceKey = DynamicDataSourceContextHolder.getDataSourceKey();
if (!DynamicDataSourceContextHolder.default_key.equals(dataSourceKey)) {
Map<Object, Object> dataSources = this.getTargetDataSources();
Object currentDataSource = dataSources.get(dataSourceKey);
if (currentDataSource == null) {
log.error("[动态数据源]通过key={}没有获取到指定的数据源,自动使用默认数据源,没有抛出异常", dataSourceKey);
}
}
return dataSourceKey;
}
/**
* 添加数据源集合
*
* @param dataSources 数据源集合
*/
public void setDataSource(Map<Object, Object> dataSources) {
this.targetDataSources.putAll(dataSources);
super.setTargetDataSources(this.targetDataSources);
super.afterPropertiesSet();
}
/**
* 所有数据源
*
* @return
*/
public Map<Object, Object> getTargetDataSources() {
return targetDataSources;
}
然后把所有数据源通过 setDataSource()方法设置进去,key是每个租户的特定编号,vlue是数据源的配置
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername(druidSourceProperties.getUsername());
druidDataSource.setPassword(druidSourceProperties.getPassword());
druidDataSource.setUrl(druidSourceProperties.getDbUrl());
druidDataSource.setInitialSize(druidSourceProperties.getInitialSize());
druidDataSource.setMinIdle(druidSourceProperties.getMinIdle());
druidDataSource.setMaxActive(druidSourceProperties.getMaxActive());
druidDataSource.setMaxWait(druidSourceProperties.getMaxWait());
druidDataSource.setUseGlobalDataSourceStat(true);
initDataSources.put(hpSysTenantInfoSelectResultVo.getTenantId(),druidDataSource);
setDataSource(initDataSources);
然后就是拦截器了,自己写个注解拦截,我是直接拦截了swagger的一个注解,都可以
@Pointcut(value = "@annotation(io.swagger.annotations.ApiOperation)")
public void hospitalWebLog() {
}
@Around(value = "hospitalWebLog()")
public Object methodAround(ProceedingJoinPoint pjp) throws Throwable {
String tenant = null;
try {
// 设置当前动态源
tenant = requestUtils.getTenantIdByHeader();
if (StringUtils.isEmpty(tenant)) {
String errMsg = identify + "当前请求头没有租户ID,无法获取数据源";
log.error(errMsg);
throw new ApiException(errMsg, HRCode.DATASOURCE_ERROR.value());
} else {
log.info(identify + "当前租户编号tenant={} ", tenant);
}
DynamicDataSourceContextHolder.setDataSourceKey(tenant);
log.info(identify + "设置当前动态数据源:{} ", tenant);
return pjp.proceed();
} finally {
DynamicDataSourceContextHolder.clearDataSource();
log.info(identify + "请求完成清除数据源:{}", tenant);
}
}
当然也可以把redis的配置放在里面,根据不同的租户切换不同的redis,还有一个单线程的上下文配置
public class DynamicDataSourceContextHolder {
public static final String default_key = "随便";
// 当前线程
private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return default_key;
}
};
/**
* 切换数据源key/设置当前线程使用的数据源key
*
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 获取当前线程的数据源
*
* @return key
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 清除数据源
*/
public static void clearDataSource() {
contextHolder.remove();
这就完成了多租户 多数据源的配置 只是记录一下
这种配置还有一个好处,我们还有一个总控制系统,通过总控制系统设置的租户编号,可以管理所有租户下的数据与统计