SpringBoot整合MybatisPlus 實現多租戶

代碼已經上傳到碼云: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)]

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容