代碼已經上傳到碼云:https://gitee.com/lezaiclub/springboot-hyper-integration.git,歡迎白嫖
引言
今天我們來聊聊多組戶
其實多租戶主要講的是數據隔離,即每個企業或用戶都享有自己的獨立數據,不和其他人的數據相互摻合,別人也是無法獲取我們自己的數據的。
多租戶在實現上主要有三種方式:
獨立數據庫
這種方式最簡單明了,每個企業或用戶在平臺上通過獨立的數據庫來隔離自己的數據,這是在物理上達到了數據的隔離,這也是它的優點所在,但是他的缺點是,為每個企業或用戶創建獨立的數據庫,成本非常大,而且空間的利用率也不高,造成嚴重的浪費??偨Y下:
- 優點:數據完全隔離、安全性高
- 缺點:成本高,數據庫多,難以維護
同一數據庫,不同表
這種方式是在邏輯上進行隔離,不同用戶的數據都在同一個數據庫中,但是使用不同的表來存儲不同用戶的數據,實現數據的隔離,這種方式相對上面,成本下降了,也同樣達到了數據隔離
同一數據庫,同一張表,通過字段區分
這種方式相對上面兩種,成本就更加少了,僅僅通過字段就可以區分不同的數據,這種方式維護簡單,成本少,但是進行數據導出和遷移,卻是一種大大的麻煩,總結下
- 優點:維護方便、成本低、實現簡單,維護的租戶數量可以有很多
- 缺點:數據好遷移,數據沒有完全做到隔離
通過對比上面三種方式,我們已經清楚了每種實現方案的區別及其他們的優劣勢,在本文,我們將通過集成mybatisPlus,實現第三種方式,來實現多租戶。
環境搭建
基于上一節的環境,我們已經搭集成了mybatisPlus的環境。
現在我們在member表中新增一個字段tenant_id,用來保存租戶信息,同樣如果你的表中需要維護租戶信息,也需要創建同樣的一個字段
ALTER TABLE `member`
ADD COLUMN `tenant_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '租戶id' AFTER `member_level`,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`) USING BTREE;
coding
添加請求上下文輔助類
這個類主要是保存當前請求用戶的的信息,使用threadlocal來實現,和當前請求線程綁定
package com.aims.mybatisplus.conf;
public class TenantRequestContext {
private static ThreadLocal<String> tenantLocal = new ThreadLocal<>();
public static void setTenantLocal(String tenantId) {
tenantLocal.set(tenantId);
}
public static String getTenantLocal() {
return tenantLocal.get();
}
public static void remove() {
tenantLocal.remove();
}
}
添加認證攔截器
這個攔截器主要是獲取請求頭中的租戶id,然后放到上下文中,供mybatisPlus獲取
package com.aims.mybatisplus.interceptor;
import com.aims.mybatisplus.conf.TenantRequestContext;
import com.mysql.cj.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("tenant_id");
if (!StringUtils.isNullOrEmpty(userId)) {
TenantRequestContext.setTenantLocal(userId);
System.out.printf("當前租戶ID:"+userId);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
配置攔截器
package com.aims.mybatisplus.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class LoginConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注冊攔截器
InterceptorRegistration registration = registry.addInterceptor(new AuthInterceptor());
registration.addPathPatterns("/**"); //所有路徑都被攔截
registration.excludePathPatterns( //添加不攔截路徑
"你的登陸路徑", //登錄
"/**/*.html", //html靜態資源
"/**/*.js", //js靜態資源
"/**/*.css", //css靜態資源
"/**/*.woff",
"/**/*.ttf"
);
}
}
添加mybatisPlus配置類
該類主要是配置mybatisPlus攔截器,用來配租戶ID字段,哪些表可以獲取租戶處理,租戶ID從上下文中獲取
package com.aims.mybatisplus.conf;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 從上下文中獲取
return new StringValue(TenantRequestContext.getTenantLocal());
}
// 這是 default 方法,默認返回 false 表示所有表都需要拼多租戶條件
@Override
public boolean ignoreTable(String tableName) {
return !"member".equalsIgnoreCase(tableName);
}
// 數據表中對應的租戶字段,這里是默認字段
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
}));
return interceptor;
}
}
當前目錄結構
編寫測試接口
注意租戶信息需要寫在請求頭里面的,
@RestController
public class TenantController {
@Autowired
private MemberMapper memberMapper;
// 測試插入操作
@RequestMapping("/testTenant")
public String testTenantId() {
Member member = new Member();
member.setMemberName("測試租戶ID");
memberMapper.insert(member);
return "success";
}
//測試查詢操作
@RequestMapping("/getCurrentTenantMember")
public List<Member> getCurrentTenantMember() {
QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
return memberMapper.selectList(queryWrapper);
}
}
最后
通過上面演示,相信大家應該都已經實現了多組戶,后面會有更多mybatisPlus實戰教程分享給大家,大家可以關注公眾號“AI碼師”領取更多面試資料及微服務全套教程。
福利大放送
關注微信公眾號“AI碼師”,領取面試資料和最新全套微服務教程
[圖片上傳失敗...(image-40605f-1636763200945)]