MyBatisPlus实现多租户功能
前言:多租户是一种软件架构技术,在多用户的环境下,共有同一套系统,并且要注意数据之间的隔离性。
一、SaaS多租户简介
1.1、SaaS多租户
SaaS,是Software-as-a-Service的缩写名称,意思为软件即服务,即通过网络提供软件服务。
SaaS平台供应商将应用软件统一部署在自己的服务器上,客户可以根据工作实际需求,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得Saas平台供应商提供的服务。
SaaS服务通常基于一套标准软件系统为成百上千的不同客户(又称为租户)提供服务。这要求SaaS服务能够支持不同租户之间数据和配置的隔离,从而保证每个租户数据的安全与隐私,以及用户对诸如界面、业务逻辑、数据结构等的个性化需求。由于SaaS同时支持多个租户,每个租户又有很多用户,这对支撑软件的基础设施平台的性能、稳定性和扩展性提出很大挑战。
1.2、多租户
多租户技术(英语:multi-tenancy technology)或称多重租赁技术,是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。
多租户技术可以实现多个租户之间共享系统实例,同时又可以实现租户的系统实例的个性化定制。通过使用多租户技术可以保证系统共性的部分被共享,个性的部分被单独隔离。通过在多个租户之间的资源复用,运营管理维护资源,有效节省开发应用的成本。
多租户技术的实现重点,在于不同租户间应用程序环境的隔离(application context isolation)以及数据的隔离(data isolation),以维持不同租户间应用程序不会相互干扰,同时数据的保密性也够强。
1.3、多租户数据隔离有三种方案
独立数据库:简单来说就是一个租户使用一个数据库,这种数据隔离级别最高,安全性最好,但是提高成本。
共享数据库、隔离数据架构:多租户使用同一个数据裤,但是每个租户对应一个Schema(数据库user)。
共享数据库、共享数据架构:使用同一个数据库,同一个Schema,但是在表中增加了租户ID的字段,这种共享数据程度最高,隔离级别最低。
如果希望以最少的服务器为最多的租户提供服务,并且租户接受以牺牲隔离级别换取降低成本。我们可以采用方案三,即共享数据库,共享数据架构,因为这种方案服务器成本最低,但是提高了开发成本。所以MybatisPlus就提供了一种多租户的解决方案,实现方式是基于多租户插件TenantLineInnerInterceptor进行实现的。
二、MybatisPlus多租户插件
MybatisPlus提供了租户处理器( TenantId 行级 ),租户之间共享数据库,共享数据架构,通过表字段(租户ID)进行数据逻辑隔离。
注意事项:
- 多租户 != 权限过滤,不要乱用,租户之间是完全隔离的!!!
- 启用多租户后所有执行的method的sql都会进行处理.
- 自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是 inner join 的要写标准的 inner join)
TenantLineInnerInterceptor是MybatisPlus中提供的多租户插件,其使用方法大致分为下面4步:
<!-- Mybatis-Plus 增强CRUD --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><!-- Mybatis-Plus 扩展插件 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-extension</artifactId><version>3.5.1</version></dependency>
2.1、表及实体类添加租户ID
租户ID一般用tenant_id
2.2、application文件中添加多租户配置和新增配置属性类
(1)设置环境变量,配置拦截规则:可以设置是否开启多租户,对多租户的表设置白名单忽略多租户拦截等。
#多租户配置
tenant:enable: truecolumn: tenant_idfilterTables:ignoreTables:- sys_app- sys_config- sys_dict_data- sys_dict_type- sys_logininfor- sys_menu- sys_notice- sys_oper_log- sys_role- sys_role_menu- sys_user- sys_user_roleignoreLoginNames:
例如sys_user表结构中,没有tenant_id多租户字段,那么多租户拦截器不拦截该表。
(2)多租户配置属性类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;/*** 多租户配置属性类** @author hege* @Date 2023-08-25**/
@Data
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {/*** 是否开启多租户*/private Boolean enable = true;/*** 租户id字段名*/private String column = "tenant_id";/*** 需要进行租户id过滤的表名集合*/private List<String> filterTables;/*** 需要忽略的多租户的表,此配置优先filterTables,若此配置为空则启用filterTables*/private List<String> ignoreTables;/*** 需要排除租户过滤的登录用户名*/private List<String> ignoreLoginNames;
}
2.3、编写多租户处理器实现TenantLineHandler接口
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;/*** 多租户处理器实现TenantLineHandler接口** @author hege* @Date 2023-08-25*/
public class MultiTenantHandler implements TenantLineHandler {private final TenantProperties properties;public MultiTenantHandler(TenantProperties properties) {this.properties = properties;}/*** 获取租户ID值表达式,只支持单个ID值 (实际应该从用户信息中获取)** @return 租户ID值表达式*/@Overridepublic Expression getTenantId() {//实际应该从用户信息中获取if(SecurityUtils.getTenantLoginUser()!=null){Long tenantId = SecurityUtils.getLoginUser().getUser().getRootPartyId();if(tenantId!=null){return new LongValue(tenantId);}}return new LongValue(0);}/*** 获取租户字段名,默认字段名叫: tenant_id** @return 租户字段名*/@Overridepublic String getTenantIdColumn() {return properties.getColumn();}/*** 根据表名判断是否忽略拼接多租户条件** 默认都要进行解析并拼接多租户条件** @param tableName 表名* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件*/@Overridepublic boolean ignoreTable(String tableName) {//忽略指定用户对租户的数据过滤List<String> ignoreLoginNames=properties.getIgnoreLoginNames();String loginName=SecurityUtils.getTenantUsername();if(null!=ignoreLoginNames && ignoreLoginNames.contains(loginName)){return true;}//忽略指定表对租户数据的过滤List<String> ignoreTables = properties.getIgnoreTables();if (null != ignoreTables && ignoreTables.contains(tableName)) {return true;}return false;}
}
对应用户服务工具类
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.List;
import java.util.stream.Collectors;/*** 安全服务工具类** @author hege*/
public class SecurityUtils {/*** 获取多租户用户**/public static LoginUser getTenantLoginUser() {try {LoginUser loginUser = null;// 获取安全上下文对象,就是那个保存在ThreadLocal里面的安全上下文对象,总是不为null(如果不存在,则创建一个authentication属性为null的empty安全上下文对象)SecurityContext securityContext = SecurityContextHolder.getContext();// 获取当前认证了的 principal(当事人) 或者 request token (令牌); 如果没有认证,会是 null,该例子是认证之后的情况Authentication authentication = securityContext.getAuthentication();if(authentication!=null){if(authentication.getPrincipal()!=null){if (authentication.getPrincipal() instanceof LoginUser) {loginUser = (LoginUser) authentication.getPrincipal();}}}return loginUser;} catch (Exception e) {e.printStackTrace();throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);}}}
2.4、MybatisPlus配置类启用多租户拦截插件
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;/*** Mybatis Plus 配置** @author hege*/
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
@EnableConfigurationProperties(TenantProperties.class)
public class MybatisPlusConfig {/*** 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor** @param tenantProperties* @return*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties tenantProperties) {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();if (Boolean.TRUE.equals(tenantProperties.getEnable())) {// 启用多租户插件拦截interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MultiTenantHandler(tenantProperties)));}// 分页插件interceptor.addInnerInterceptor(paginationInnerInterceptor());// 乐观锁插件interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());// 阻断插件interceptor.addInnerInterceptor(blockAttackInnerInterceptor());return interceptor;}}
2.5、特定SQL语句忽略拦截
在一些场景下,无需多租户拦截,或者对于一些超级管理员使用的接口,希望跨租户查询、免数据鉴权时,可以通过下面几种方式实现忽略拦截:
-
使用MybatisPlus框架自带的@InterceptorIgnore注解,以用在Mapper类上,也可以用在方法上
-
添加超级用户账号白名单,在自定义的Handler里进行逻辑判断,跳过拦截
-
添加数据表白名单,在自定义的Handler里进行逻辑判断,跳过拦截
/*** 使用@InterceptorIgnore注解,忽略多租户拦截 <br/>* 注解@InterceptorIgnore可以用在Mapper类上,也可以用在方法上** @param id* @return*/@InterceptorIgnore(tenantLine = "true")UserOrgVO myFindByIdNoTenant(@Param(value = "id") Long id);
验证结果:
针对MybatisPlus提供的API、自定义Mapper中的statement均可正常拦截,会在SQL执行增删改查的时候自动加上tenant_id。
参考链接:
数据权限拦截器,多租户拦截器
Mybatis-Plus入门系列(4)- MybatisPlus之多租户插件TenantLineInnerInterceptor_mybatis plus tenant
使用MyBatisPlus实现多租户功能
相关文章:

MyBatisPlus实现多租户功能
前言:多租户是一种软件架构技术,在多用户的环境下,共有同一套系统,并且要注意数据之间的隔离性。 一、SaaS多租户简介 1.1、SaaS多租户 SaaS,是Software-as-a-Service的缩写名称,意思为软件即服务&#x…...
JAVA-斐波那契数列
输入一个整数 n ,求斐波那契数列的第 n 项。 假定从 0 开始,第 0 项为 0 。 数据范围 0≤n≤39 样例 输入整数 n5 返回 5class Solution {public int Fibonacci(int n) {int[] dpnew int[250];dp[0]0;dp[1]1;for(int i2;i<n;i){dp[i]dp[i-1]dp[i-2];}…...

keepalived+lvs(DR)
目录 一,作用 二,调度器配置 1,安装keepalived 2, 安装ipvsadm 3, 配置keepalived 4. 查看lvs节点状态 5, web节点配置 1.1 调整ARP参数 1.2 配置虚拟IP地址 1.3添加回环路由 1.4安装nginx并写…...

基于Matlab实现频谱分析(附上源码+数据集)
Matlab是一个功能强大的数值计算和科学计算软件,可以用于频谱分析。频谱分析是一种信号处理技术,用于将时域信号转换为频域信号,以便更好地理解信号的频率特性。本文将介绍使用Matlab实现频谱分析的方法。 文章目录 部分源码完整源码数据集下…...

【Java】多线程(进阶)
多线程进阶 常见的所策略乐观锁和悲观锁重量级锁和轻量级锁自旋锁和挂起等待锁自旋锁挂起等待锁 读写锁和互斥锁读写锁互斥锁 公平锁和非公平锁公平锁非公平锁 可重入锁和不可重入锁可重入锁不可重入锁 CASCAS应用实现原子类实现自旋锁 CAS的ABA问题 synchronized原理基本特点加…...

BMP图片读写实践:rgb转bgr
本实理论上支持24位图和32位图,实际上只测试了24位。原理很简单,就是RGB中的蓝色字节和红色字节交换。 测试代码1: #include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <stdlib.h> #include &l…...

交通科技与管理杂志社交通科技与管理编辑部2023年第9期目录
专家论坛 黑龙江省经济高质量发展与生态环境保护耦合协调发展研究 刘降斌;祃玉帅; 1-5142 我国省际数字经济高质量发展水平综合评价研究 耿娟;毕晨曦; 6-8 振兴龙江《交通科技与管理》投稿邮箱:cn7kantougao163.com(注明投稿“《交通科技与管理》”) 数…...

根据源码,模拟实现 RabbitMQ - 网络通讯设计,实现客户端Connection、Channel(完结)
目录 一、客户端代码实现 1.1、需求分析 1.2、具体实现 1)实现 ConnectionFactory 2)实现 Connection 3)实现 Channel 二、编写 Demo 2.1、实例 2.1、实例演示 一、客户端代码实现 1.1、需求分析 RabbitMQ 的客户端设定ÿ…...
The Cube++ Illumination Estimation Dataset 文章总结
The Cube++ Illumination Estimation Dataset 颜色恒常性数据集Cube++ Type: Academic Journal Author: Ershov Link: https://ieeexplore.ieee.org/document/9296220 Select: ⭐️⭐️⭐️⭐️ Status: Done 备注: Cube++数据集 Journal: ACCESS Year: 2020 code: https://g…...
“烧钱”的大模型,如何迈过存储这道坎?
几乎每一个行业都在讨论大模型,每一个行业巨头都在训练大模型,人工智能已然进入了大模型主导的时代。 想要占领大模型应用的高地,数据和算力可以说是不可或缺的基石。和算力相关的讨论已经有很多,以至于英伟达的市值在2023年翻了…...

UNIX网络编程卷一 学习笔记 第二十九章 数据链路访问
目前大多操作系统都为程序提供访问数据链路层的功能,此功能可提供以下能力: 1.能监视由数据链路层接收的分组,使得tcpdump之类的程序能运行,而无需专门的硬件设备来监视分组。如果结合使用网络接口进入混杂模式(promis…...

WebGIS的一些学习笔记
一、简述计算机网络的Internet 概念、网络类型分类、基本特征和功用是什么 计算机网络的Internet 概念 计算机网络是地理上分散的多台独立自主的计算机遵循约定的通讯协议,通过软、硬件互连以实现交互通信、资源共享、信息交换、协同工作以及在线处理等功能的系统…...

java Spring Boot将不同配置拆分入不同文件管理
关于java多环境开发 最后还有一个小点 我们一般会将不同的配置 放在不同的配置文件中 好处肯定就在于 想换的时候非常方便 那么 我们直接看代码 我们将项目中的 application.yml 更改代码如下 spring:profiles:active: dev这里 意思是 我们选择了dev 环境 然后创建一个文件 …...

Docker(三) 创建Docker镜像
一、在Docker中拉取最基本的Ubuntu系统镜像 搜索Ubuntu镜像 Explore Dockers Container Image Repository | Docker Hub 下载镜像 docker pull ubuntu:22.04 二、在镜像中添加自己的内容 使用ubuntu镜像创建容器 docker run -it ubuntu:20.04 /bin/bash 在容器中创建了一个文…...

Linux操作系统--shell编程(正则表达式)
1..正则表达式概述 正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在 Linux 中,grep,sed,awk 等文本处理工具都支持通过正则表达式进行模式匹配。 2.常规的匹配操作 3.…...
k8s的service mesh功能有那些
Kubernetes(K8s)的服务网格(Service Mesh)是一种用于管理微服务架构中服务通信、安全性、可观察性等方面的工具集合。服务网格通过将网络和安全功能从应用程序代码中分离出来,帮助简化了微服务的部署和管理。以下是一些…...

【数据库技术】NineData数据复制,加速实时数仓构建
8 月 30 日,由 NineData 和 SelectDB 共同举办的主题为“实时数据驱动,引领企业智能化数据管理”的线上联合发布会,圆满成功举办!双方聚焦于实时数据仓库技术和数据开发能力,展示如何通过强大的生态开发兼容性…...

Kotlin入门1. 语法基础
Kotlin入门1. 语法基础 一、简介二、在Idea创建一个示例项目三、基本语法1. 第一个程序2. 基本数据类型(1) 数字(2) 类型转换(3) 数学运算位运算 (4)可空类型 3. 函数4. 字符串(1) 字符串拼接(2) 字符串查找(3) 字符串替换(4) 字符串分割 5. null 安全的…...
MVCC简介、工作流程、优缺点
目录 简介 相关概念 工作流程 MVCC优缺点 简介 MVCC(Multi-Version Concurrency Control)即多版本并发控制,是通过维护数据的历史版本,从而解决并发访问情况下的读一致性问题 相关概念 读锁: 也叫共享锁、S锁。若…...
pandas由入门到精通-pandas的数据结构
pandas数据分析-pandas的数据结构 pandas 数据结构Series1. 创建Series数组2. 性质3. 索引4. 运算DataFrame1. 创建Df数组2. 性质3.索引4. 对列进行增删改Index Objects本文介绍pandas中一些常用的属性方法的概述,给读者提供快速学习的架构和思路。表格中提供的一些参数方法没…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...