SpringBoot整合MybatisPlus 实现多租户

代码已经上传到码云:https://gitee.com/lezaiclub/springboot-hyper-integration.git,欢迎白嫖

引言

今天我们来聊聊多组户
其实多租户主要讲的是数据隔离,即每个企业或用户都享有自己的独立数据,不和其他人的数据相互掺合,别人也是无法获取我们自己的数据的。
多租户在实现上主要有三种方式:

独立数据库

这种方式最简单明了,每个企业或用户在平台上通过独立的数据库来隔离自己的数据,这是在物理上达到了数据的隔离,这也是它的优点所在,但是他的缺点是,为每个企业或用户创建独立的数据库,成本非常大,而且空间的利用率也不高,造成严重的浪费。总结下:

  • 优点:数据完全隔离、安全性高
  • 缺点:成本高,数据库多,难以维护

同一数据库,不同表

这种方式是在逻辑上进行隔离,不同用户的数据都在同一个数据库中,但是使用不同的表来存储不同用户的数据,实现数据的隔离,这种方式相对上面,成本下降了,也同样达到了数据隔离

同一数据库,同一张表,通过字段区分

这种方式相对上面两种,成本就更加少了,仅仅通过字段就可以区分不同的数据,这种方式维护简单,成本少,但是进行数据导出和迁移,却是一种大大的麻烦,总结下

  • 优点:维护方便、成本低、实现简单,维护的租户数量可以有很多
  • 缺点:数据好迁移,数据没有完全做到隔离

通过对比上面三种方式,我们已经清楚了每种实现方案的区别及其他们的优劣势,在本文,我们将通过集成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阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容