前面做了一个基于dynamic-datasource实现读写分离的实现,基于dynamic-datasource实现多数据源的读写分离_邋遢道的博客-CSDN博客
最近公司遇到一个多租户的的需求,刚好就继续使用dynamic-datasource来做多数据源的切换
多租户就是说多个租户共用一个实例,租户的数据既有隔离又有共享,从而解决数据存储的问题,其实我们常用的权限那种也可以理解为多租户
多租户的三种实现方案:
1.独立数据库
即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本也高。(本文实现方式就是这种)
2.共享数据库,独立 Schema
多个或所有租户共享Database,但是每个租户一个Schema(也可叫做一个user)
3.共享数据库,共享 Schema,共享数据表
这个其实就类似权限了,所有租户用一个表,通过一个id字段进行区分
三种方式各有各的优缺点,土豪建议用独立数据库,其他的就看业务需求,好了大概了解了,接下来我们来了解下如何切换根据租户来自由切换数据库。
前面有一章也写过了直接配置的多数据源,基本的内容如果有需要就去翻一翻。
想要自由切换主要有两个步骤就搞定,第一步是要新增数据源,第二步就是切换数据源
新增:
/**
* @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);
}
}