在SaaS系统中,很多都会使用MybatisPlus的多租户插件,快速地实现多租户的数据隔离。
该插件主要提供2件功能:
- 对查询SQL的每个表过滤租户id
- 保存数据时,给租户id赋值
对于这2个功能,MybatisPlus提供了一个接口
public interface TenantLineHandler {
/**
* 获取租户 ID 值表达式,只支持单个 ID 值
*
* @return 租户 ID 值表达式
*/
Expression getTenantId();
/**
* 获取租户字段名
* 默认字段名叫: tenant_id
*
* @return 租户字段名
*/
default String getTenantIdColumn() {
return "tenant_id";
}
/**
* 根据表名判断是否忽略拼接多租户条件
* 默认都要进行解析并拼接多租户条件
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
*/
default boolean ignoreTable(String tableName) {
return false;
}
}
如果要使用的话,需要实现该接口,并提供如何获取租户id的逻辑,这样MybatisPlus知道租户id的值后,通过Mybatis的插件就可以实现以上2个功能。
实现租户处理器(TenantLineHandler)后,这个类是不归Spring管理的,不会被注入到容器中管理,所以我们需要定一个配置类,让MybatisPlus绑定这个处理器。如下:
@Configuration
@MapperScan("com.yourpackage.mapper")
public class MybatisPlusConfig {
@Autowired
private CustomTenantHandler customTenantHandler;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(customTenantHandler);
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}
执行如上步骤后,假设当前tenant_id=‘test_value’,我们执行任一SQL,就可以看到:
select * from table_a ;
变为了
select * from table_a where tenant_id = 'test_value';
执行insert语句时,不需要指定 tenant_id字段的值,但是保存后,在数据库可以查询到,tenant_id的值变为 ‘test_value’ 了。
其实,还有“逻辑删除插件”,也是基于类似的方式配置,然后交给MybatisPlus插件来实现的。
租户插件是为了保证租户间的操作隔离的。但是,有的时候我们是不需要过滤租户id的。例如:我有个定时任务,我需要执行所有租户的某个功能。
此时,总不能每添加一个租户,我就创建一个定时任务吧?
这个时候,我们就可以通过MybatisPlus提供的注解来实现该功能。
该注解代码如下:
package com.baomidou.mybatisplus.annotation;
import java.lang.annotation.*;
/**
* 内置插件的一些过滤规则
* <p>
* 支持注解在 Mapper 上以及 Mapper.Method 上
* 同时存在则 Mapper.method 比 Mapper 优先级高
* <p>
* 支持:
* true 和 false , 1 和 0 , on 和 off
* <p>
* 各属性返回 true 表示不走插件(在配置了插件的情况下,不填则默认表示 false)
*
* @author miemie
* @since 2020-07-31
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface InterceptorIgnore {
/**
* 行级租户 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor}
*/
String tenantLine() default "";
/**
* 动态表名 {@link com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor}
*/
String dynamicTableName() default "";
/**
* 攻击 SQL 阻断解析器,防止全表更新与删除 {@link com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor}
*/
String blockAttack() default "";
/**
* 垃圾SQL拦截 {@link com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor}
*/
String illegalSql() default "";
/**
* 数据权限 {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor}
* <p>
* 默认关闭,需要注解打开
*/
String dataPermission() default "1";
/**
* 分表 {@link com.baomidou.mybatisplus.extension.plugins.inner.ShardingInnerInterceptor}
*/
String sharding() default "";
/**
* 其他的
* <p>
* 格式应该为: "key"+"@"+可选项[false,true,1,0,on,off]
* 例如: "xxx@1" 或 "xxx@true" 或 "xxx@on"
* <p>
* 如果配置了该属性的注解是注解在 Mapper 上的,则如果该 Mapper 的一部分 Method 需要取反则需要在 Method 上注解并配置此属性为反值
* 例如: "xxx@1" 在 Mapper 上, 则 Method 上需要 "xxx@0"
*/
String[] others() default {};
}
InterceptorIgnore用在Mapper方法上,功能是:
- 对该Mapper方法,明确指明一些插件在该方法上是否失效
- 对该Mapper方法,明确指定一些自定义插件在该方法上是否失效
例如以“多租户插件”为例,如下就是表明,该方法不适用“多租户插件”,不会自动补充上tenant_id的过滤。
@InterceptorIgnore(tenantLine = "true")
List<User> findAllUser();
如果是自定义的插件,假设插件的字段为custom_filter_field,可以使用如下方式来实现
@InterceptorIgnore(tenantLine = "true",others = {"custom_filter_field@true"})