当前位置: 首页 > news >正文

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实现多租户功能

前言&#xff1a;多租户是一种软件架构技术&#xff0c;在多用户的环境下&#xff0c;共有同一套系统&#xff0c;并且要注意数据之间的隔离性。 一、SaaS多租户简介 1.1、SaaS多租户 SaaS&#xff0c;是Software-as-a-Service的缩写名称&#xff0c;意思为软件即服务&#x…...

JAVA-斐波那契数列

输入一个整数 n &#xff0c;求斐波那契数列的第 n 项。 假定从 0 开始&#xff0c;第 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)

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

基于Matlab实现频谱分析(附上源码+数据集)

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

【Java】多线程(进阶)

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

BMP图片读写实践:rgb转bgr

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

交通科技与管理杂志社交通科技与管理编辑部2023年第9期目录

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

根据源码,模拟实现 RabbitMQ - 网络通讯设计,实现客户端Connection、Channel(完结)

目录 一、客户端代码实现 1.1、需求分析 1.2、具体实现 1&#xff09;实现 ConnectionFactory 2&#xff09;实现 Connection 3&#xff09;实现 Channel 二、编写 Demo 2.1、实例 2.1、实例演示 一、客户端代码实现 1.1、需求分析 RabbitMQ 的客户端设定&#xff…...

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…...

“烧钱”的大模型,如何迈过存储这道坎?

几乎每一个行业都在讨论大模型&#xff0c;每一个行业巨头都在训练大模型&#xff0c;人工智能已然进入了大模型主导的时代。 想要占领大模型应用的高地&#xff0c;数据和算力可以说是不可或缺的基石。和算力相关的讨论已经有很多&#xff0c;以至于英伟达的市值在2023年翻了…...

UNIX网络编程卷一 学习笔记 第二十九章 数据链路访问

目前大多操作系统都为程序提供访问数据链路层的功能&#xff0c;此功能可提供以下能力&#xff1a; 1.能监视由数据链路层接收的分组&#xff0c;使得tcpdump之类的程序能运行&#xff0c;而无需专门的硬件设备来监视分组。如果结合使用网络接口进入混杂模式&#xff08;promis…...

WebGIS的一些学习笔记

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

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&#xff08;K8s&#xff09;的服务网格&#xff08;Service Mesh&#xff09;是一种用于管理微服务架构中服务通信、安全性、可观察性等方面的工具集合。服务网格通过将网络和安全功能从应用程序代码中分离出来&#xff0c;帮助简化了微服务的部署和管理。以下是一些…...

【数据库技术】NineData数据复制,加速实时数仓构建

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

Kotlin入门1. 语法基础

Kotlin入门1. 语法基础 一、简介二、在Idea创建一个示例项目三、基本语法1. 第一个程序2. 基本数据类型(1) 数字(2) 类型转换(3) 数学运算位运算 &#xff08;4&#xff09;可空类型 3. 函数4. 字符串(1) 字符串拼接(2) 字符串查找(3) 字符串替换(4) 字符串分割 5. null 安全的…...

MVCC简介、工作流程、优缺点

目录 简介 相关概念 工作流程 MVCC优缺点 简介 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;即多版本并发控制&#xff0c;是通过维护数据的历史版本&#xff0c;从而解决并发访问情况下的读一致性问题 相关概念 读锁&#xff1a; 也叫共享锁、S锁。若…...

pandas由入门到精通-pandas的数据结构

pandas数据分析-pandas的数据结构 pandas 数据结构Series1. 创建Series数组2. 性质3. 索引4. 运算DataFrame1. 创建Df数组2. 性质3.索引4. 对列进行增删改Index Objects本文介绍pandas中一些常用的属性方法的概述,给读者提供快速学习的架构和思路。表格中提供的一些参数方法没…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...