多租户模式设计

 大家好,最近有个需求,就是一套系统,给不同公司使用,还要数据隔离,所以就设计了这种多租户模式,使用请求不同,访问的数据源也不同的多租户模式,下面让我们来一起看一下,是否对你有所启发...

1.首先说下设计思路

先默认连接一个数据库,数据库里面有一个数据源配置表,配置了所有租户的不同数据源,在启动项目的时候初始化连接这些数据源,放在一个集合中,然后不同租户的请求要在请求头设置一个参数,在请求的时候拦截这个参数,来控制访问的数据源,就是这么简单。

2.来具体看下

首先设置一套动态数据源保存地方

public class DynamicDataSource extends AbstractRoutingDataSource {

    // 存储数据源
    private Map targetDataSources = new HashMap();

    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }

    /**
     * 配置DataSource, defaultTargetDataSource为主数据库
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }


    @Override
    protected Object determineCurrentLookupKey() {
        Object dataSourceKey = DynamicDataSourceContextHolder.getDataSourceKey();
        if (!DynamicDataSourceContextHolder.default_key.equals(dataSourceKey)) {
            Map dataSources = this.getTargetDataSources();
            Object currentDataSource = dataSources.get(dataSourceKey);
            if (currentDataSource == null) {
                log.error("[动态数据源]通过key={}没有获取到指定的数据源,自动使用默认数据源,没有抛出异常", dataSourceKey);
            }
        }
        return dataSourceKey;
    }

    /**
     * 添加数据源集合
     *
     * @param dataSources 数据源集合
     */
    public void setDataSource(Map dataSources) {
        this.targetDataSources.putAll(dataSources);
        super.setTargetDataSources(this.targetDataSources);
        super.afterPropertiesSet();
    }



    /**
     * 所有数据源
     *
     * @return
     */
    public Map 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 contextHolder = new InheritableThreadLocal() {
        @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();

这就完成了多租户 多数据源的配置 只是记录一下

这种配置还有一个好处,我们还有一个总控制系统,通过总控制系统设置的租户编号,可以管理所有租户下的数据与统计

你可能感兴趣的:(java)