前言
以前公司的项目是每个城市使用不同的数据库,从物理上就解决了不同城市用户(租户)带来的数据隔离的问题。现在要求各个城市的数据部署在一个数据库中,在同一张表中,因此需要对现有的工程进行多租户模式改造。而基于修改最少代码的原则下,想到了使用Mybatis-Plus的增强功能是否能够实现对sql语句的自定义修改,可喜的是当我进入Mybatis-Plus的官网,正好看到了有对多租户模式的支持。下面来介绍如何将你的项目升级支持多租户模式。
1. 引入Mybatis-Plus的依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
2. 编写核心Handler
package xxx;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author xxx
* 租户维护处理器
*/
@Slf4j
@Component
public class TenantHandler implements TenantLineHandler {
@Autowired
private TenantConfigProperties properties;
/**
* 获取租户值
* <p>
*
* @return 租户值
*/
@Override
public Expression getTenantId() {
String tenantId = TenantContextHolder.getTenantId();
log.debug("当前租户为 >> {}", tenantId);
if (tenantId == null) {
return new NullValue();
}
return new StringValue(tenantId);
}
/**
* 获取租户字段名
*
* @return 租户字段名
*/
@Override
public String getTenantIdColumn() {
return properties.getColumn();
}
/**
* 根据表名判断是否进行过滤
*
* @param tableName 表名
* @return 是否进行过滤
*/
@Override
public boolean ignoreTable(String tableName) {
String tenantId = TenantContextHolder.getTenantId();
// 租户中ID 为空,查询全部,不进行过滤
if (tenantId == null) {
return Boolean.TRUE;
}
return !properties.getTables().contains(tableName);
}
}
TenantHandler需要实现TenantLineHandler接口,重写其中的三个方法:
- getTenantId():MyBatis-Plus 获取租户值的方法,需要返回正确的租户Id,前端的每次请求我都放到了TenantContextHolder的threadLocal中,每次从threadLocal中获取。
- getTenantIdColumn(): 这里需要获取到数据库表中的租户字段, MyBatis-Plus框架会根据此方法获取该字段名称。
- ignoreTable():根据表名判断是否对该表进行多租户的处理,返回false代表该表需要进行多租户处理,true表示忽略。
3. 编写TenantContextHolder
package com.aipark.aiot.ao.common.datesource.tenant;
import lombok.experimental.UtilityClass;
@UtilityClass
public class TenantContextHolder {
private final ThreadLocal<String> THREAD_LOCAL_TENANT = new ThreadLocal<>();
/**
* TTL 设置租户ID
*
* @param tenantId
*/
public void setTenantId(String tenantId) {
THREAD_LOCAL_TENANT.set(tenantId);
}
/**
* 获取TTL中的租户ID
*
* @return
*/
public String getTenantId() {
return THREAD_LOCAL_TENANT.get();
}
public void clear() {
THREAD_LOCAL_TENANT.remove();
}
}
4. 编写租户配置类
package xxx;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 多租户配置
*
* @author xxx
*/
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "config.tenant")
public class TenantConfigProperties {
/**
* 维护租户列名称
*/
private String column;
/**
* 多租户的数据表集合
*/
private List<String> tables = new ArrayList<>();
}
5. 添加MybatisPlusInterceptor拦截器
package xxx;
import com.aipark.aiot.ao.common.datesource.config.MybatisPlusMetaObjectHandler;
import com.aipark.aiot.ao.common.datesource.resolver.SqlFilterArgumentResolver;
import com.aipark.aiot.ao.common.datesource.tenant.TenantLineInterceptor;
import com.aipark.aiot.ao.common.datesource.tenant.TenantHandler;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @author
* @className MybatisAutoConfiguration
* @description MybatisAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
public class MybatisAutoConfiguration implements WebMvcConfigurer {
@Autowired
private TenantHandler tenantHandler;
/**
* 分页和租户插件, 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加多租户配置
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
6. yml配置
config:
tenant:
column: tenant_id
tables:
- sys_role
- sys_config
到此,你的项目便可以支持多租户模式啦~