Springboot+SpringSecurity+mybatis-plus项⽬实现多租户。。
欢迎⼤家去我的个⼈⽹站踩踩
⼀、前⾔
前⾯曾经写过⼀篇Springboot项⽬实现多租户的⽅案,当时⽤的是每个租户独⽴数据库,通过切换数据源的⽅式来实现,看这篇,这篇我们说⼀下,⽅案三通过共享数据库,共享数据库表,使⽤字段来区分不同租户,此⽅案数据隔离性差,但是成本最低。
⼆、实现思路
其实同⼀数据库表,要实现多租户,思路如下:
1、数据库表通过增加⼀个租户id字段(tenant_id)来区分不同的数据;
2、⽤户在登录后要将⽤户所属租户id保存在当前登录⽤户信息中;
3、⽤户访问接⼝时,获取当前⽤户的租户id;
4、在所有需要区分租户的数据库操作sql 语句 where 条件都加上 and tenant_id = xxxx ;
这样就只会操作⾃⼰所属租户的数据,只是如果我们⼿动在每个sql后都⾃⼰加上租户条件太繁琐了,所以我们可以通过实现。
⽤过Mybatis我们知道,对于Mybatis为我们提供了⼀个Interceptor接⼝,可以实现拦截sql语句,可能我们之前已经⽤过分页来实现分页的功能,具体的⽤法这⾥就不多说了,可以⾃⼰查⼀下,了解⼀下。
三、mybatis-plus多租户
我们这⾥直接选⽤ mybatis-plus ,它已经为我们实现了多租户的,我们看⼀下具体⽤法:
(⼀)数据库增加区分字段
⾸先为每张表(所有需要区分租户的表)增加⼀个 tenant_id 字段,⽤来区分租户,我们通过查询所有表拼接出添加语句,这样可以⼀次性为所有表添加字段
SELECT
concat( 'ALTER TABLE ', table_schema, '.', table_name, ' ADD COLUMN tenant_id varchar(100) NULL;' )
FROM
information_schema.TABLES t
WHERE
table_schema = '当前数据库';
(⼆)配置 mybatis-plus
这⾥使⽤ mybatis-plus 3.2.0 ,多租户的实现是在分页⾥的,配置如下:
package fig;
import parser.ISqlParser;
import parser.ISqlParserFilter;
import parser.SqlParserHelper;
import ptions.ApiException;
import sion.parsers.BlockAttackSqlParser;
import sion.plugins.PaginationInterceptor;
import ant.TenantHandler;
import ant.TenantSqlParser;
import enant.TenantInfoHolder;
import enant.TenantInfoHolder;
slf4j.Slf4j;
import net.pression.Expression;
import net.pression.StringValue;
import org.apachemons.lang3.StringUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.flection.MetaObject;
batis.spring.annotation.MapperScan;
import t.annotation.Bean;
import t.annotation.Configuration;
import ansaction.annotation.EnableTransactionManagement;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: guomh
* @Date: 2019/11/06
* @Description: mybatis配置*
*/
@Slf4j
@EnableTransactionManagement
@Configuration
@MapperScan({"cn.mukanyun.base.dao","cn.mukanyun.*.*.mapper"})
public class MybatisPlusConfig {
/**
* 加载分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override
public Expression getTenantId(boolean where) {
// 该 where 条件 3.2.0 版本开始添加的,⽤于分区是否为在 where 条件中使⽤            String currentUserTenantId = TenantId();
log.info("获取租户id:"+currentUserTenantId);
if(StringUtils.isBlank(currentUserTenantId)) {
throw new ApiException("获取当前租户id为空!");
}
return new StringValue(currentUserTenantId);
springboot是啥}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean doTableFilter(String tableName) {
// 这⾥可以判断是否过滤表
if ("tenant_info".equalsIgnoreCase(tableName)
|| "t_role".equalsIgnoreCase(tableName)
) {
return true;
}
return false;
}
}
});
sqlParserList.add(tenantSqlParser);
// 攻击 SQL 阻断解析器、加⼊解析链
sqlParserList.add(new BlockAttackSqlParser());
paginationInterceptor.setSqlParserList(sqlParserList);
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = MappedStatement(metaObject);
// 过滤⾃定义查询此时⽆租户信息约束出现
if (
"lement.ByResId".Id())                ||"rder.untDeptOrder".Id())                ) {
return true;
}
return false;
}
});
return paginationInterceptor;
}
}
我们来分析⼀下上⾯的配置⽂件:
1、 PaginationInterceptor (AbstractSqlParserHandler)
PaginationInterceptor 分页,继承了 AbstractSqlParserHandler
我们看 AbstractSqlParserHandler ⾥⾯有两个成员变量 sqlParserList、sqlParserFilter sqlParserFilter 是⽤来过滤需要处理的sql语句,sqlParserList 是sql处理器列表
所以我们看上⾯ mybatis 配置⽂件的分页插件⾥:
1)先构造了 ⼀个sqlParserList ,往⾥添加了⼀个处理器 TenantSqlParser
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
sqlParserList.add(tenantSqlParser);
然后设置到分页⾥
paginationInterceptor.setSqlParserList(sqlParserList);
2)然后构造了⼀个匿名内部类 SqlParserFilter 设置到了分页⾥
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {...});
这样两个成员变量都有了,下⾯我们看⼀下这两个成员变量具体⽤法。
2、TenantSqlParser
我们分析⼀下TenantSqlparser,从名字我们就可以看出,这个是处理租户的sql语句的,我们先看看源码