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

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个租户标识(如 tenant_id),并在所有的数据库查询中自动加入这个租户ID作为过滤条件来实现的。

使用MyBatis-Plus实现了多租户功能

1、MybatisPlusConfig.java:配置类,通过TenantLineInnerInterceptor开启租户功能。该拦截器是关键,能自动向SQL查询添加WHERE条件。

public class MybatisPlusConfig {@Autowiredprivate TenantProperties tenantProperties;@Autowiredprivate MyTenantLineHandler myTenantLineHandler;@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();if (tenantProperties.isEnable()) { // 根据配置文件决定是否启用多租户插件TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor();tenantLineInnerInterceptor.setTenantLineHandler(myTenantLineHandler); // 设置我们自定义的处理器interceptor.addInnerInterceptor(tenantLineInnerInterceptor);System.out.println("INFO: TenantLineInnerInterceptor has been added to MybatisPlusInterceptor."); // 用于启动时确认}// 分页插件interceptor.addInnerInterceptor(paginationInnerInterceptor());// 乐观锁插件interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());// 阻断插件interceptor.addInnerInterceptor(blockAttackInnerInterceptor());return interceptor;}/*** 分页插件,自动识别数据库类型*/public PaginationInnerInterceptor paginationInnerInterceptor() {PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();// 设置数据库类型为mysqlpaginationInnerInterceptor.setDbType(DbType.MYSQL);// 设置最大单页限制数量,默认 500 条,-1 不受限制paginationInnerInterceptor.setMaxLimit(-1L);return paginationInnerInterceptor;}/*** 乐观锁插件*/public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {return new OptimisticLockerInnerInterceptor();}/*** 如果是对全表的删除或更新操作,就会终止该操作*/public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {return new BlockAttackInnerInterceptor();}
}

2、MyTenantLineHandler.java:自定义的TenantLineHandler实现,负责提供租户ID、列名以及指定需要过滤的表。它通过TenantUtils.getCurrentTenantId()获取租户ID,并从TenantProperties获取列名,同时判断是否需要对特定表应用过滤器。

package com.ruoyi.framework.config;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.ruoyi.common.utils.TenantUtils;
import com.ruoyi.framework.config.properties.TenantProperties;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Set;/*** 这个类实现了 MyBatis Plus 的 TenantLineHandler 接口,负责两件事:* 告诉拦截器当前操作的租户ID是什么。* 告诉拦截器当前操作的表是否应该被忽略。*/
@Component
public class MyTenantLineHandler implements TenantLineHandler {private static final Logger log = LoggerFactory.getLogger(MyTenantLineHandler.class);@Autowiredprivate TenantProperties tenantProperties;/*** 【核心修改】* 只有当用户选择了租户时,才返回真实的租户ID。* 否则,我们返回一个特殊的、无效的ID(如0L),并配合 shouldFilter 方法来确保拦截器不工作。*/@Overridepublic Expression getTenantId() {Long currentTenantId = TenantUtils.getCurrentTenantId();if (currentTenantId == null) {// 当没有租户ID时,返回一个不可能匹配任何记录的ID// 真实的过滤逻辑由 shouldFilter 控制return new LongValue(0L); }return new LongValue(currentTenantId);}/*** 获取租户ID的数据库字段名*/@Overridepublic String getTenantIdColumn() {return tenantProperties.getColumn(); // 从配置文件获取}/*** 这是一个总开关。只有当用户登录并明确选择了某个租户后,才允许拦截器进行SQL处理。* 否则,拦截器应忽略所有表。* @param tableName 表名* @return 是否忽略。true = 忽略,false = 不忽略(进行处理)*/@Overridepublic boolean ignoreTable(String tableName) {// 如果用户还未选择租户,则忽略所有表Long currentTenantId = TenantUtils.getCurrentTenantId();if (currentTenantId == null) {log.debug("No tenant selected, ignoring all tables.");return true;}// 如果用户已选择租户,则根据配置文件中的 ignore-tables 列表来判断Set<String> ignoreTables = tenantProperties.getIgnoreTables();boolean shouldIgnore = ignoreTables != null && ignoreTables.stream().anyMatch(item -> item.equalsIgnoreCase(tableName.trim()));if (shouldIgnore) {log.debug("Table [{}] is in ignore-tables list, skipping.", tableName);} else {log.debug("Table [{}] is not in ignore-tables list, applying tenant filter.", tableName);}return shouldIgnore;}
}

3、TenantProperties.java:从配置文件加载多租户配置,包括是否启用、租户列名(tenant_id)和忽略的表。

package com.ruoyi.framework.config.properties;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;@Component
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {/*** 是否开启多租户功能*/private boolean enable = true; // 默认开启,可以在YML中覆盖/*** 多租户 ID 的数据库字段名*/private String column = "tenant_id"; // 默认列名为 tenant_id/*** 需要忽略多租户处理的表名集合 (在配置文件中用逗号分隔)*/private Set<String> ignoreTables = new HashSet<>();// --- Getters and Setters ---public boolean isEnable() {return enable;}public void setEnable(boolean enable) {this.enable = enable;}public String getColumn() {return column;}public void setColumn(String column) {this.column = column;}public Set<String> getIgnoreTables() {return ignoreTables;}// 这个setter允许YML中用逗号分隔的字符串配置 ignoreTablespublic void setIgnoreTables(String ignoreTablesStr) {if (ignoreTablesStr != null && !ignoreTablesStr.isEmpty()) {this.ignoreTables = Set.of(ignoreTablesStr.toLowerCase().split(",")).stream().map(String::trim).collect(Collectors.toSet());} else {this.ignoreTables = new HashSet<>();}}
}

整个拦截流程 

请求进入SysProjectController后,最终调用projectMapper.selectProjectList(...)。MyBatis-Plus的TenantLineInnerInterceptor会拦截该调用,通过MyTenantLineHandler获取当前租户ID和列名,然后检查sys_project表是否需要过滤。如果需要,拦截器会自动在SQL查询的WHERE子句中添加AND tenant_id = ?,从而确保只返回当前租户的数据。 这个“拦截”行为不是在业务代码(如 Controller 或 Service)中能直接看到的,它是通过 Spring Boot 的自动配置 和 MyBatis 的插件机制 在底层实现的。具体如下:

  1.  注册拦截器 (应用启动时):在 ruoyi-framework 模块中看到的 MybatisPlusConfig.java 文件是这一切的起点。这是一个配置类。interceptor.addInnerInterceptor(tenantLineInnerInterceptor)是“连接点”!这行代码明确地将配置好的 TenantLineInnerInterceptor 注册 到了 MyBatis-Plus 的拦截器链中。

    当 Spring Boot 应用启动时,它会扫描到这个配置,执行 mybatisPlusInterceptor() 方法,创建一个包含了租户拦截器的 MybatisPlusInterceptor Bean。

  2. MyBatis 插件工作原理:

    MyBatis 自身设计了一套插件(Interceptor)机制,它允许在 SQL 执行过程的某些关键节点进行干预。这些节点包括:Executor: SQL 执行器、StatementHandler: SQL 语法构建处理器、ParameterHandler: 参数处理器、ResultSetHandler: 结果集处理器。TenantLineInnerInterceptor 正是利用了这套机制,它主要拦截的是 StatementHandler。

  3. SQL 执行时的拦截过程 (请求处理时) :前端应用发送一个请求;经过Controller -> Service -> Mapper;MyBatis 执行;进入拦截点: 在 MyBatis 将 XML 中的 SQL (select ... from sys_project ...) 转换成最终可以在数据库执行的 PreparedStatement 之前,它会触发 StatementHandler 的处理流程;TenantLineInnerInterceptor 生效: 因为我们在启动时已经注册了 TenantLineInnerInterceptor,此时它就会被激活。拦截器拿到原始的 SQL 语句。它调用我们提供的 MyTenantLineHandler,获取到当前的 tenantId (例如 123) 和租户列名 (tenant_id)。它会智能地分析原始 SQL,找到 FROM sys_project 这部分,并自动在 WHERE 条件中(或者新建一个 WHERE 条件)补充上租户过滤。

总结

拦截的动作发生在 MyBatis 执行 SQL 的生命周期内部,而不是在业务代码层面。MybatisPlusConfig.java 中的配置,就像是给 MyBatis 的执行引擎装上了一个“插件”或“mod”。一旦装上,它就会对所有经过的 SQL “自动审查和加工”,无需在每次调用 Mapper 时手动干预。

这种设计的好处是 透明和无侵入:业务开发人员只需要关注业务逻辑,而不需要关心多租户的过滤细节,框架会自动保证数据安全,大大减少了编码工作量和出错的可能性。

相关文章:

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...