功能要求
Mybatis-plus配置多租户。
从header中的token获取租户信息;
所有userController的数据库操作*不要*配置多租户,所有工作计划相关的数据库操作*需要*配置多租户,达成数据隔离。
开发工具:IDEA
技术:Springboot+MabtisPlus+Postman
1、什么是多租户?
多租户技术(多重租赁技术,简称saas),是一种软件架构技术;可以实现同一套程序下用户数据的隔离。
2、实现方案
简单看:就是通过在sql的where条件语句拼接租户id,获取和操作对应租户的数据记录
2.1 添加依赖
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
2.2 从header的token中获取租户信息
这里采用 自定义上下文组件传递租户id
2.2.1 自定义上下文
package com.example.demo.config;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author
* @Date 2022/8/2 11:26
* @Description 自定义上下文
*/
@Component
public class MyContext {
private static final String KEY_CURRENT_TENANT_ID="KEY_CURRENT_PROVIDER_ID";
private static final Map<String,Object> M_CONTEXT=new ConcurrentHashMap<>();
public void setCurrentTenantId(String tenantId){
M_CONTEXT.put(KEY_CURRENT_TENANT_ID,tenantId);
}
public String getCurrentTenantId(){
return (String) M_CONTEXT.get(KEY_CURRENT_TENANT_ID);
}
}
2.2.1 获取租户信息到上下文中
在上篇博文的拦截器的token校验成功位置进行添加。
校验成功,获取相应租户信息;
jwtUtils.verify(token);//验证token
//获取租户信息到上下文中
Map<String,Claim> claims= jwtUtils.parseJwtTo(token);
String userId=claims.get("userId").asString();
myContext.setCurrentTenantId(userId);
return true; //上一句无异常就 放行
2.3 自定义实现租户处理器
实现步骤:
在需要隔离的数据表上增添租户id——>实现TenantHandler接口(已废弃,现在用TenantLineHandler)并实现它的方法
(当前数据库有两张表,分别为sys_user和userworkplan,此次就是仅对userworkplan配置多租户;两张表中共有字段userId,所以不需额外添加tenant_id字段,可直接使用userId作为租户功能使用)
package com.example.demo.common;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.example.demo.config.MyContext;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @Author
* @Date 2022/8/2 10:07
* @Description 自定义实现租户处理器
*/
@Component
@Slf4j
public class MyTenantHandler implements TenantLineHandler {
private static final List<String> IGNORE_TENANT_TABLES = Arrays.asList("`sys_user`");//不配置多租户功能的表;这里需要注意表名不要写错,是否需要``根据自己所写的sql语句来定
//sql语句中``的作用就是与数据库的关键字区分
/**
* 获取租户 ID 值表达式,只支持单个 ID 值
* @return 租户 ID 值表达式
*/
@Override
public Expression getTenantId() {
// 1.从上下文中获取租户ID
// 2.可以通过过滤器从请求中获取对应租户id
// 3.从header中的token获取租户信息(✔)——>1
MyContext myContext=new MyContext();
String tenantId = myContext.getCurrentTenantId();
log.info("当前租户ID:{}", tenantId);
if (Objects.isNull(tenantId)) {
return new NullValue();
}
return new StringValue(tenantId);
}
/**
* 获取租户字段名
* 默认字段名叫: tenant_id
* 数据库中字段用的userId
* @return 租户字段名
*/
@Override
public String getTenantIdColumn() {
return "userId";
}
/**
* 根据表名判断是否忽略拼接多租户条件
* 默认都要进行解析并拼接多租户条件
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
*/
@Override
public boolean ignoreTable(String tableName) {
return IGNORE_TENANT_TABLES.stream().anyMatch(ignoreTableName -> ignoreTableName.equalsIgnoreCase(tableName));
}
}
2.4 进行mybatisplus的配置
package com.example.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.example.demo.common.MyTenantHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Objects;
/**
* @Author
* @Date 2022/8/2 9:59
* @Description mybatis plus配置
*/
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {"com.example.demo.mapper"})
public class MybatisPlusConfiguration {
/**
* 配置拦截器(包括分页拦截器)
* 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户拦截器
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantHandler()));
// 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
// 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
3、学习到的知识点
- @Component实现bean的注入
- 多租户技术的简单实现:①自定义租户处理器 ②进行mybatisplus配置
- log.info()打印日志
- Claim数据格式
- Java打印正在执行的sql语句到控制台方法
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl