当前位置: 首页 > 工具软件 > Dynamic-proxy > 使用案例 >

基于dynamic-datasource多数据源自由切换--多租户

寇鸿
2023-12-01

前面做了一个基于dynamic-datasource实现读写分离的实现,基于dynamic-datasource实现多数据源的读写分离_邋遢道的博客-CSDN博客

最近公司遇到一个多租户的的需求,刚好就继续使用dynamic-datasource来做多数据源的切换

多租户

多租户就是说多个租户共用一个实例,租户的数据既有隔离又有共享,从而解决数据存储的问题,其实我们常用的权限那种也可以理解为多租户

多租户的三种实现方案:

        1.独立数据库

                即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本也高。(本文实现方式就是这种)

        2.共享数据库,独立 Schema

                多个或所有租户共享Database,但是每个租户一个Schema(也可叫做一个user)

        3.共享数据库,共享 Schema,共享数据表

                这个其实就类似权限了,所有租户用一个表,通过一个id字段进行区分

三种方式各有各的优缺点,土豪建议用独立数据库,其他的就看业务需求,好了大概了解了,接下来我们来了解下如何切换根据租户来自由切换数据库。

dynamic-datasource多数据源自由切换

前面有一章也写过了直接配置的多数据源,基本的内容如果有需要就去翻一翻。

想要自由切换主要有两个步骤就搞定,第一步是要新增数据源,第二步就是切换数据源

新增:

/**
     * @Author andy
     * @Description 通过传入数据库信息进行新增数据源
     * @Date 10:59 2022/4/23
     * @Param 
     * @return
     **/
    public void addDataSource(JSONObject jsonObject){
        DataSourceProperty property = new DataSourceProperty();
        property.setUrl("jdbc:mysql://"+jsonObject.getString("dataIp")+":"+jsonObject.getString("dataPort")+"/"+jsonObject.getString("dataName"));
        property.setUsername(jsonObject.getString("dataUser"));
        property.setPassword(jsonObject.getString("dataPassword"));

        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        DataSource dataSource = dataSourceCreator.createDataSource(property);
        ds.addDataSource(jsonObject.getString("dataName"), dataSource);
    }

切换:

        //切换数据源
        DynamicDataSourceContextHolder.push("上一步的数据库名称");
        //中间的业务操作。。。
        //可以最后选择清理掉此数据源
        DynamicDataSourceContextHolder.clear();

核心的代码就这么点。比较简单吧,下面贴一下全部的代码,先简单说下实现流程:

用户登录时,我将数据库的信息传入了token,当访问具体业务时,使用aop的方式进行数据源切换,业务操作完成,清理掉数据源,还是继续使用默认数据源。当然,这个实现其实会有一些小问题,比如频繁的去加数据源,切换清理等,实际使用可以自行调整


import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.util.Map;

/**
 * @ClassName DynamicDataSourceAspect
 * @Description TODO
 * @Author andy
 * @Date 2022/4/22 15:33
 * @Version 1.0
 */
@Slf4j
@Aspect
@Component
@Order(0) // 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private DefaultDataSourceCreator dataSourceCreator;


    private final String tenant = "tenant";

    @Around("execution(* com.system.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        String token = SecurityUtils.getToken();
        LoginUser loginUser = AuthUtil.getLoginUser(token);
        if(loginUser == null){
            //尚未登录
            Object result = jp.proceed();
            return result;
        }
        Map<String, Object> tenantNo = loginUser.getTenantNo();
        if(tenantNo.isEmpty()){
            //租户信息不存在则使用默认数据源
            Object result = jp.proceed();
            return result;
        }
        //上面这一段代码需要获取token信息,自行修改,或者删除,主要是数据源要怎么来,可配置,可传入
        //需要切换数据源
        JSONObject jsonObject = (JSONObject) tenantNo.get(tenant);

        log.info("当前租户Id:{}", jsonObject.getString("dataName"));
        addDataSource(jsonObject);
        //切换数据源
        DynamicDataSourceContextHolder.push(jsonObject.getString("dataName"));
        Object result = jp.proceed();
        DynamicDataSourceContextHolder.clear();
        return result;
    }

    /**
     * @Author andy
     * @Description //根据tenant新增到数据源中
     * @Date 10:59 2022/4/23
     * @Param
     * @return
     **/
    public void addDataSource(JSONObject jsonObject){
        DataSourceProperty property = new DataSourceProperty();
        property.setUrl("jdbc:mysql://"+jsonObject.getString("dataIp")+":"+jsonObject.getString("dataPort")+"/"+jsonObject.getString("dataName"));
        property.setUsername(jsonObject.getString("dataUser"));
        property.setPassword(jsonObject.getString("dataPassword"));

        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        DataSource dataSource = dataSourceCreator.createDataSource(property);
        ds.addDataSource(jsonObject.getString("dataName"), dataSource);
    }
}

 类似资料: