Mybatis Plus 多租户id使用

news2025/1/13 6:12:38

本文就不多逼逼,直接进入正题。

什么是多租户

多租户技术(Multi-TenancyTechnology)又称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下 (此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。简单讲: 在一台服务器上运行单个应用实例,它为多个租户(客户)提供服务。从定义中我们可以理解:多租户是一种架 构,目的是为了让多用户环境下使用同一套程序,且保证用户间数据隔离。那么重点就很浅显易懂了,多租户的重 点就是同一套程序下实现多用户数据的隔离

隔离方案

目前基于多租户的数据库设计方案通常有如下三种:

  • 1、独立数据库 共享数据库

  • 2、独立 Schema 共享数据库

  • 3、共享数据库、共享数据表

独立数据库

即一个租户一个数据库。

优点

为不同的租户提供独立的数据库,用户数据隔离级别最高,安全性最好,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果出现故障,恢复数据比较简单。

缺点

数据库维护成本和购置成本的大大增加。

共享数据库,独立 Schema

即多个或所有租户共享Database,每个租户一个Schema。

什么是Schema

  • oracle数据库:在oracle中一个数据库可以具有多个用户,那么一个用户一般对应一个Schema,表都是建立在 Schema 中的,(可以简单的理解:在 oracle 中一个用户一套数据库表)

  • mysql数据库:mysql数据中的schema比较特殊,并不是数据库的下一级,而是等同于数据库。比如执行 create schema test 和执行create database test效果是一模一样的

优点

为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可以支持更多的租户数量。

缺点

如果出现故障,数据恢复比较困难,因为恢复数据库将牵扯到其他租户的数据;

如果需要跨租户统计数据,存在一定困难。 这种方案是方案一的变种。只需要安装一份数据库服务,通过不同的Schema对不同租户的数据进行隔离。由于数 据库服务是共享的,所以成本相对低廉。

共享数据库、共享数据表

即租户共享同一个Database、同一个Schema,但在表中通过tenant_id字段区分租户的数据,表明该记录是属于哪个租户的。这是共享程度最高、隔离级别最低的模式。

优点

所有租户使用同一套数据库,所以成本低廉。

缺点

隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量;这种方案和基于传统应用的数据库设计并没有任何区别,但是由于所有租户使用相同的数据库表,所以需要做好对 每个租户数据的隔离安全性处理,这就增加了系统设计和数据管理方面的复杂程度。 数据备份和恢复最困难,需要逐表逐条备份和还原。

如果希望以最少的服务器为最多的租户提供服务,并且租户接受以牺牲隔离级别换取降低成本,这种方案最适合。

集成

本文选择的是方案三!如果是自己从零开始进行开发,需要在每条 sql 上加上 tenant_id 条件。那开发成本特别大。但我们使用的是 Mybatis Plus,那就不需要如此复杂了,框架已经集成多租户使用。

创建表

CREATE TABLE `test_tenant` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `account` varchar(32) DEFAULT NULL,
  `email` varchar(64) DEFAULT NULL,
  `tenant_id` int(10) unsigned DEFAULT NULL COMMENT '租户id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

insert 语句

INSERT INTO `test`.`test_tenant`(`id`, `account`, `email`, `tenant_id`) VALUES (1, 'cxyxj', 'cxyxj.qq.com', 0);
INSERT INTO `test`.`test_tenant`(`id`, `account`, `email`, `tenant_id`) VALUES (2, 'awesome', 'awesome@163.com', 1);
INSERT INTO `test`.`test_tenant`(`id`, `account`, `email`, `tenant_id`) VALUES (3, 'gongj', 'gongj@163.com', 2);
复制代码

注意关键字段tenant_id

搭建项目

依赖

搭建 Boot项目,加入以下依赖:

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <mybatis-plus.version>3.5.0</mybatis-plus.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
复制代码

实体

@Data
public class TestTenant {

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String account;

    private String email;

    private Integer tenantId;

}
复制代码

我们的主键类型为 int,所以需要修改主键策略,修改为自增,默认使用雪花算法生成全局唯一id,长度为19 位。

mapper接口

public interface TenantMapper extends BaseMapper<TestTenant> {
}
复制代码

MybatisPlusConfig

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.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                //获得当前登录用户的租户id
                return new LongValue(1111);
            }
        }));
        return interceptor;
    }
}
复制代码

在 Mybatis Plus 中,一切插件的主体是 InnerInterceptor。 目前已有的功能(官网地址):

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql 性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

本文使用到的是 TenantLineInnerInterceptor。在我们的代码中,使用了 TenantLineInnerInterceptor 类的有参构造方法。入参为 TenantLineHandler对象。这是比较重要的对象,比如:某一些表不需要拼接多租户条件、多租户的字段名是什么。都是在这个对象中规定。

public interface TenantLineHandler {
    // 获得租户ID值  本文写死了 111
    Expression getTenantId();

   // 数据库字段 默认为 tenant_id
    default String getTenantIdColumn() {
        return "tenant_id";
    }
   // 需要忽略拼接条件的表名
   // 方法默认返回 false 表示所有表都需要拼多租户条件
    default boolean ignoreTable(String tableName) {
        return false;
    }

  // 这个方法在之前版本是没有的!已给出租户列的 insert 不再拼接条件。使用用户给出的值。
 // 针对比较特殊的场景,比如:异步添加时,获取不到登录人的租户ID,则给默认租户ID
    default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
        return columns.stream().map(Column::getColumnName).anyMatch((i) -> {
            return i.equalsIgnoreCase(tenantIdColumn);
        });
    }
}
复制代码

配置文件

server:
  port: 1998
spring:
  datasource:
    url: jdbc:mysql:/127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8
    username: root
    password: xxx
    driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码

测试

@SpringBootTest
public class MybatisPlusApplicationTests {

    @Autowired
    TenantMapper tenantMapper;

    @Test
    public void testSelect() {
        List<TestTenant> testTenants = tenantMapper.selectList(null);
        testTenants.forEach(System.out::println);
    }
}
复制代码

关注控制台打印的 sql 语句,在 where 语句后面拼接了 test_tenant.tenant_id = 1111的条件。这说明我们的租户隔离达到效果,并且很轻松容易的实现了。

测试增修删语句

那我们再来看看其他语句!

@Test
public void testOther() {
    System.out.println("测试新增=====");
    TestTenant testTenant = new TestTenant();
    testTenant.setAccount("hhhh");
    testTenant.setEmail("100093");
    tenantMapper.insert(testTenant);
    System.out.println("测试修改=====");
    testTenant.setEmail("@164.com");
    tenantMapper.updateById(testTenant);
    System.out.println("测试删除=====");
    tenantMapper.deleteById(testTenant.getId());

}
复制代码
  • 测试新增
测试新增=====
==>  Preparing: INSERT INTO test_tenant (account, email, tenant_id) 
VALUES (?, ?, 1111)
==> Parameters: hhhh(String), 100093(String)
<==    Updates: 1
复制代码
  • 测试修改
测试修改=====
==>  Preparing: UPDATE test_tenant SET account = ?, email = ? WHERE 
test_tenant.tenant_id = 1111 AND id = ?
==> Parameters: hhhh(String), @164.com(String), 4(Integer)
<==    Updates: 1
复制代码
  • 测试删除
测试删除=====
==>  Preparing: DELETE FROM test_tenant WHERE test_tenant.tenant_id = 1111 AND id = ?
==> Parameters: 4(Integer)
<==    Updates: 1
复制代码

可以得知,当配置了 TenantLineInnerInterceptor插件后,我们的 CRUR SQL 都拼接了我们所指定的字段作为 where 条件。

特殊处理

在实际的开发中,肯定不会如此的一帆风顺。肯定会有一些比较特殊的逻辑。

某表不需要拼接租户条件

总有一些表是比较特殊的。表中压根就没租户id字段,那这怎么处理呢? 我们只需要重写 TenantLineHandler 类中的ignoreTable方法即可。

/**
 * 需要忽略拼接多租户条件的表名
 */
@Value("#{'${mybatis-plus.configuration.ignore-tenant-tables:}'.split(',')}")
private List<String> ignoreTenantTables;


// 该 default 方法 默认返回 false 表示所有表都需要拼多租户条件
// 如果有部分 sql 不需要加上租户ID条件
// 可以使用 @InterceptorIgnore(tenantLine = "true") 标注在 Mapper 接口的方法上
// 而 @SqlParser(filter = true) 在 mybatis-plus 3.4 版本中标记为过时
@Override
public boolean ignoreTable(String tableName) {
    return ignoreTenantTables.stream().anyMatch((e) -> e.equalsIgnoreCase(tableName));
}
复制代码

在配置文件中将需要忽略的表名进行配置。

mybatis-plus:
  configuration:
    ignore-tenant-tables: test_tenant
复制代码

测试

@Test
public void testSelect() {
    List<TestTenant> testTenants = tenantMapper.selectList(null);
    testTenants.forEach(System.out::println);
}
复制代码

可以看到这一次的查询语句中并没有拼接多租户条件。

某一条sql不需要拼接

对于一些拥有租户id字段的表,在某一些场景中,比如:我想获得表中所有数据,不想让它拼接条件。那应该怎么做?注意:这种都是自己自定义 sql 语句。

我们只需要在自定义的方法上标注一个注解 @InterceptorIgnore。这是官方提供的。

该注解作用于 xxMapper.java 方法之上,各属性代表对应的插件,各属性不给值则默认为 false,设置为 true 表示忽略拦截。

自定义 sql

@Select("SELECT id, account, email, tenant_id FROM test_tenant")
@InterceptorIgnore(tenantLine = "true")
List<TestTenant> listAll();
复制代码

测试

@Test
public void listAll() {
    List<TestTenant> testTenants = tenantMapper.listAll();
    testTenants.forEach(System.out::println);
}
复制代码

额外知识点

TenantLineHandler 类中,还有一个方法没有介绍,那就是 ignoreInsert方法。这个方法的作用就是如果你在进行 insert 时,我们手动给了租户id字段,则框架不再自动拼接。我们来看看效果吧!

@Test
public void testInsert() {
    System.out.println("测试新增=====");
    TestTenant testTenant = new TestTenant();
    testTenant.setAccount("hhhh");
    testTenant.setEmail("100093");
    testTenant.setTenantId(11232323);
    tenantMapper.insert(testTenant);
}
复制代码

可以看到 sql 中 tenant_id 的值,取的是我们指定的值。 我们看看源码是怎么处理的!逻辑在 processInsert方法中。

如果需要插入的列中,包含知道的租户列,则不进行多租户处理。

如果还想对 update、delete sql 也进行这种特殊的处理,只需要重写对应的方法 processUpdateprocessDelete


  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/45326.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud服务配置介绍Nacos实现管理配置

目录 一、服务配置中心介绍 二、Nacos config入门 三、Nacos config深入 四、Nacos的几个概念 一、服务配置中心介绍 首先我们来看一下,微服务架构下关于配置文件的一些问题&#xff1a; 配置文件相对分散。在一个微服务架构下&#xff0c;配置文件会随着微服务的增多变的…

uniapp 智能安装(自动升级)插件 Ba-SmartUpgrade

简介&#xff08;下载地址&#xff09; Ba-SmartUpgrade 是一款用于智能安装&#xff08;自动升级&#xff09;的插件&#xff0c;无需用户的任何操作就可以自动安装程序。 说明 在app升级更新&#xff0c;覆盖安装时&#xff0c;系统会弹出授权提示弹窗&#xff0c;需要用户…

两种PDF密码都忘记了,怎么办?

PDF文件的两种密码&#xff1a;打开密码、编辑限制 两种密码加密PDF文件后的效果是不一样的&#xff1a; 设置了打开密码的PDF文件&#xff0c;是在打开文件的时候需要输入密码&#xff0c;输入了正确的PDF密码&#xff0c;进入到文件之后&#xff0c;就一些都正常了&#xf…

Pr:编辑字幕

对于添加好的字幕&#xff0c;可在文本面板中的“字幕”选项卡、时间轴面板或节目面板上进行编辑。需要时&#xff0c;可在基本图形面板中改变字幕的样式。“字幕”选项卡中显示了当前活动字幕轨道上的各个字幕分段的编号、时间码范围、文本内容等。单击左下角的“ABC”按钮可改…

Word文档的两种密码忘记了,怎么办?

Word文档的密码也有两种&#xff1a;一种是打开密码&#xff0c;一种是编辑限制 两种密码加密后的效果也是不一样的&#xff1a; 设置了打开密码的Word文档&#xff0c;是在打开文件的时候需要输入密码&#xff0c;保护文件内容不被其他人看到。当我们输入了正确的Word密码&a…

【元胞自动机】元胞自动机短消息网络病毒传播仿真【含Matlab源码 1289期】

⛄一、元胞自动机简介 1 元胞自动机发展历程 最初的元胞自动机是由冯 诺依曼在 1950 年代为模拟生物 细胞的自我复制而提出的. 但是并未受到学术界重视. 1970 年, 剑桥大学的约翰 何顿 康威设计了一个电脑游戏 “生命游戏” 后, 元胞自动机才吸引了科学家们的注意. 1983 年…

第一个maven项目(idea)

配置Maven 确保idea与你要使用的maven版本不冲突&#xff0c;否则使用idea内置即可。 手工创建Java项目 在test目录下&#xff0c;新建resources&#xff0c;如果不是测试资源根&#xff0c;右键将目录标记为&#xff1a; 原型创建Java项目 原型创建Web项目 插件 配置tomcat插…

数据结构与算法之《单链表》详解

标题&#xff1a;单链表的思路及代码实现 作者&#xff1a;Ggggggtm 寄语&#xff1a;与其忙着诉苦&#xff0c;不如低头赶路&#xff0c;奋路前行&#xff0c;终将遇到一番好风景 文章目录&#xff1a; 引入 一、链表的概念及结构 1.1 链表的概念 1.2 链表的结构 二、链表的思…

单文件组件:dom高亮插件、在父组件中引入子组件、App.vue代码代码写法

输入<template>等dom为什么会有高亮显示&#xff1f; 下载Vetur插件&#xff1a; 模板会自动帮你导出&#xff0c;但是js文件不会&#xff0c;需要你手动导出&#xff0c;启动的服务器&#xff0c;只会热更新&#xff0c;如果想要刷新整个页面就自己手动刷新&#xff1b…

2022.11.29总结

今天写了条件查询 虽然思路上还说是比较顺&#xff0c;但是还是写了一晚上&#xff0c;因为老是在细节上出现bug&#xff0c;改了好久&#xff0c;踩了好几个坑。 首先大概是因为组件不是确定的&#xff0c;我把ref属性绑定在router-view上&#xff0c;导致我获取不到条件选择…

[附源码]Python计算机毕业设计SSM基于Java的校园二手平台交易系统(程序+LW)

环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 Maven管理等…

SpringBoot+html+vue模板开发备忘录

除了对某个表基本的增删改查以外&#xff0c;可能需要额外的增加操作&#xff0c;这里是通过按钮来实现的 1、新增一个测试按钮 <el-button type"primary" class"butT" click"test()">测试</el-button> 2、这个按钮绑定一个方法t…

CFDP:聚类算法

Clustering by Fast Search and Find of Density Peaks(CFDP) - 发表于2014 science期刊 聚类算法&#xff0c;作为机器学习里常用的一种无监督方法&#xff0c;一直以来都受到很大的关注。聚类算法&#xff0c;是希望把同一类的样本或者样本聚到一起&#xff0c;比如说常见的…

BIRT 横向分栏

【问题】 I have a table which will displays User ids in one column. I want to display that column as two columns Ex. In First Row, First user id in Column 1 and Second user id in Column2, In Second Row, second user id in Column 1 and third user id in Co…

教育机构客户管理系统功能方案详解!

前言&#xff1a; 教育机构客户管理系统怎么做&#xff1f;哪个好&#xff1f;还在为客户流失而烦恼的你&#xff0c;一定很困恼&#xff0c;别人是怎么留住客户的呢&#xff1f;今天就来告诉你。 随着我国产业结构升级&#xff0c;在政策顶层推动和经济主体需求转变的共同影…

nlp工具库spacy

文章目录spacy能做什么如何安装案例 分词功能spacy是一个辅助自然语言处理的工具库。 spacy能做什么 它集成了各种实用的句子分析功能&#xff0c;包括分词、词性分析、词性还原等等&#xff0c;所有功能特性可参考官网 spacy-101的features一章&#xff0c;有Tokenization、…

ROS MarkerArray的几种常见用法

ros使用过程中&#xff0c;经常会用到rviz可视化各种数据。而在rviz中&#xff0c;marker与markerarray是常用的一种可视化工具&#xff0c;最近也用到过几次了&#xff0c;这里随手记录一下。 1、makerarray画线 在marker中常见的就是表示两点之间的连线&#xff0c;而marke…

【LeetCode】1752. 检查数组是否经排序和轮转得到

题目描述 给你一个数组 nums 。nums 的源数组中&#xff0c;所有元素与 nums 相同&#xff0c;但按非递减顺序排列。 如果 nums 能够由源数组轮转若干位置&#xff08;包括 0 个位置&#xff09;得到&#xff0c;则返回 true &#xff1b;否则&#xff0c;返回 false 。 源数组…

怎么提高外贸开发信的回复率?

写客户开发信是每个外贸人都必备的技能。对于成本预算小的企业来讲&#xff0c;开发信是性价比非常高的一种方式。但是&#xff0c;很多人在写完开发信之后并没有收获到比较好的回复效果&#xff0c;可能是因为他们没有把握写开发信的技巧。怎么提高外贸开发信的回复率&#xf…

挖掘数据价值,华为云大数据BI解决方案有绝招

在没有接触电子商务时&#xff0c;就一直很好奇&#xff0c;他们是怎么知道我需要什么并及时推送给我的&#xff0c;直到后来自己也开始做电商的时候&#xff0c;才知道原来电子商务必不可少的是大数据分析方案。在这里我强烈给同业者们安利一下华为云的大数据BI解决方案&#…