基于mybatis-plus历史背景下的多租户平台改造

前言

别误会,本篇【并不是】 要用mybatis-plus自身的多租户方案:在表中加一个tenant_id字段来区分不同的租户数据。并不是的!
而是在假设业务系统已经使用mybatis-plus多数据源的前提下,如何实现业务数据库隔开的多租户系统。
这里面有点绕:多数据源可以是一个系统本身的功能需求,假设当前系统算做是个单租户,它使用了两个数据库: master1和sys1,那么做多租户改造后,假设现在有了2个租户,那么就要添加2个数据库:master2和sys2 , 总共就是四个数据库(数据源)了…
咱们这里简单化处理,假设一个业务系统只使用一个数据库

大纲

在本篇我们可以

  • 看到mybatis-plus底层多数据源的实现原理
  • 在不破坏多数据源的前提下,实现多租户功能
  • spring security结合jwt记录租户信息

代码版本:

springboot: 2.7.0
dynamic-datasource-spring-boot-starter: 4.3.0
io.jsonwebtoken: 0.12.3

回顾mybatis-plus多数据源使用

1.yaml配置:
在这里插入图片描述
2.serviceImpl:
在这里插入图片描述
或者使用切面动态设置crud对应的数据源。

改造需求

  • 不要把所有租户信息都直接放在yaml等配置文件中
  • 可动态的添加删除数据源
  • 用户登录成功后,把租户信息封装到jwt token中,后续业务访问提取中租户信息,动态切换数据源访问

方案

租户本身的信息可放在resources/tenants目录下,一个租户使用一个单独的配置文件,或者通过读取另外的数据库获取。本篇先使用前者。
修改某个租户的配置文件内容/数据库,重启服务/通过controller接口触发数据源的变更。

正篇开始
yaml配置文件中只保留一个主数据库,如上yaml截图所示。
另外的2个租户配置放classpath下tenant目录,如下所示:
在这里插入图片描述
新建多数据源配置类,内容如下:

@Configuration(proxyBeanMethods = false)
@Slf4j
public class MultiDataSourceConfig {

    @Resource
    private DruidDataSourceCreator druidDataSourceCreator;

    @Resource
    private DynamicRoutingDataSource dataSource;

    @PostConstruct
    public void init() {

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            org.springframework.core.io.Resource[] resources = resolver.getResources("classpath:tenants/*.properties");

            for (org.springframework.core.io.Resource resource : resources) {
                Properties tenantProperties = new Properties();
                tenantProperties.load(resource.getInputStream());

                String tenantId = tenantProperties.getProperty("name");
                DataSourceProperty dataSourceProperty = new DataSourceProperty();
                dataSourceProperty.setPoolName(tenantId);
                dataSourceProperty.setUrl(tenantProperties.getProperty("datasource.url"));
                dataSourceProperty.setUsername(tenantProperties.getProperty("datasource.username"));
                dataSourceProperty.setPassword(tenantProperties.getProperty("datasource.password"));
                dataSourceProperty.setDriverClassName(tenantProperties.getProperty("datasource.driver-class-name"));

                dataSource.addDataSource(tenantId, druidDataSourceCreator.createDataSource(dataSourceProperty));
            }

        } catch (IOException exp) {
            throw new RuntimeException("Problem in tenant datasource:" + exp);
        }
    }
}

上述代码读取classpath:tenants/目录下的所有.properties配置文件内容,组装并添加数据源。

登录

登录操作时,查询此登录用户对应的租户信息,并在生成jwt token时,把租户信息也封装进去,通过http响应头返回给用户。如下所示:

public static void addToken(HttpServletResponse res, String username, String tenant) {
        String JwtToken = Jwts.builder()
                .subject(username)
                .audience().add(tenant).and()
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                .signWith(SIGNINGKEY)
                .compact();
        res.addHeader("Authorization", PREFIX + JwtToken);
    }

业务操作

在一个拦截器类中:token校验,并从中提取出租户信息,如下所示:

public static String getTenant(HttpServletRequest req) {

        String token = req.getHeader("Authorization");
        if (token == null) {
            return null;
        }
        String tenant = Jwts.parser()
                .setSigningKey(SIGNINGKEY)
                .build().parseClaimsJws(token.replace(PREFIX, "").trim())
                .getBody()
                .getAudience()
                .iterator()
                .next();

        return tenant;
    }
动态切换数据源

得到一个请求所属租户信息后,要访问数据库时切换源:记住这里
记住它:DynamicDataSourceContextHolder

    DynamicDataSourceContextHolder.push(tenant);
	try {
	    chain.doFilter(request, response);
	} finally {
	    DynamicDataSourceContextHolder.clear();
	}

完工!是的,使用层面上就结束了。接下来是原理分析。

剖析

1.有个类名叫:AbstractRoutingDataSource,mybatis-plus和spring-jdbc都有叫这个名的类,
并且它们都继承了AbstractDataSource类,但是这个父类也只是同名而已。但是它们的功能都说得很清楚:抽象动态获取数据源,它们都有个抽象方法:抽象获取连接池,如下所示:
在这里插入图片描述
spring-jdbc下的源码
在这里插入图片描述
然后看看mybatis-plus的抽象方法实现
在这里插入图片描述
在这里插入图片描述
所以回顾我们业务代码的写法:DynamicDataSourceContextHolder.push(tenant);
正是我们把当前请求对应的tenant作为数据源key 压栈了,后面切换数据源时依据它去得到数据源。那么还记得这个租户key 是在哪里和数据源对应上的吗
正是在正篇开头的新建多数据源配置类中:

dataSource.addDataSource(tenantId, druidDataSourceCreator.createDataSource(dataSourceProperty));
    /**
     * 添加数据源
     *
     * @param ds         数据源名称
     * @param dataSource 数据源
     */
    public synchronized void addDataSource(String ds, DataSource dataSource) {
        DataSource oldDataSource = dataSourceMap.put(ds, dataSource);
        // 新数据源添加到分组
        this.addGroupDataSource(ds, dataSource);
        // 关闭老的数据源
        if (oldDataSource != null) {
            closeDataSource(ds, oldDataSource, graceDestroy);
        }
        log.info("dynamic-datasource - add a datasource named [{}] success", ds);
    }

如此就打通了流程。如果大家感兴趣,可以看到com.baomidou.dynamic.datasource.DynamicRoutingDataSource类中有一些方法删除数据源,还有数据源分组功能,这可以用于主主(从,如果业务场景都是只读的话),策略是轮询和随机:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ高级篇之MQ可靠性 数据持久化

文章目录 消息丢失的原因分析内存存储的缺陷如何确保 RabbitMQ 的消息可靠性?数据持久化的三个方面持久化对性能的影响持久化实验验证性能对比Spring AMQP 默认持久化总结 消息丢失的原因分析 RabbitMQ 默认使用内存存储消息,但这种方式带来了两个主要问…

Openssl1.1.1s rpm包构建与升级

rpmbuild入门知识 openssh/ssl二进制升级 文章目录 前言一、资源准备1.下载openssh、openssl二进制包2.安装rpmbuild工具3.拷贝源码包到SOURCES目录下4.系统开启telnet,防止意外导致shh无法连接5.编译工具安装6.补充说明 二、制作 OpenSSL RPM 包1.编写 SPEC 文件2.…

【Unity3D】apk加密(global-metadata.dat加密)

涉及:apk、aab、global-metadata.dat、jks密钥文件、APKTool、zipalign 使用7z打开apk文件观察发现有如下3个针对加密的文件。 xxx.apk\assets\bin\Data\Managed\Metadata\global-metadata.dat xxx.apk\lib\armeabi-v7a\libil2cpp.so xxx.apk\lib\arm64-v8a\libil…

[免费]微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端)【论文+源码+SQL脚本】

大家好,我是java1234_小锋老师,看到一个不错的微信小程序(高校就业)招聘系统(Springboot后端Vue管理端),分享下哈。 项目视频演示 【免费】微信小程序(高校就业)招聘系统(Springboot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…

RNN心脏病预测-Pytorch版本

本文为为🔗365天深度学习训练营内部文章 原作者:K同学啊 一 导入数据 import numpy as np import pandas as pd import torch from torch import nn import torch.nn.functional as F import seaborn as sns from sklearn.preprocessing import Standard…

科普CMOS传感器的工作原理及特点

在当今数字化成像的时代,图像传感器无疑是幕后的关键 “功臣”,它宛如一位神奇的 “光影魔法师”,通过光电效应这一奇妙的物理现象,将光子巧妙地转换成电荷,为图像的诞生奠定基础。而在众多类型的图像传感器中&#xf…

Ubuntu 18.04 解决screen无法滚屏的问题

Ubuntu 18.04 解决screen无法滚屏的问题_ubuntu screen 无法上滑-CSDN博客文章浏览阅读2.7k次,点赞2次,收藏3次。在etc/screenrc中加入termcapinfo xterm* ti:te重新进入screen的sessionscreen -d -r XXX_ubuntu screen 无法上滑https://blog.csdn.net/w…

2025年01月09日Github流行趋势

1. 项目名称:khoj 项目地址url:https://github.com/khoj-ai/khoj项目语言:Python历史star数:22750今日star数:1272项目维护者:debanjum, sabaimran, MythicalCow, aam-at, eltociear项目简介:你…

Spring boot接入xxl-job

Spring boot接入xxl-job 导入maven包加入配置增加配置类创建执行器类&#xff08;写job的业务逻辑&#xff09;去控制台中配置job 导入maven包 <dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>…

Cglib动态代理中method.invoke与methodProxy.invokeSuper区别浅尝

前段时间看了黑马Spring教程中&#xff0c;有期视频讲解Cglib动态代理。 代码如下图&#xff1a; 可以看到调用目标对象的方法代码为&#xff1a; method.invoke(target,objects);在其他地方看到的此处代码是&#xff1a; methodProxy.invokeSuper(o,objects);注意&#xff…

【Linux 之一 】Linux常用命令汇总

Linux常用命令 ./catcd 命令chmodclearcphistoryhtoplnmkdirmvpwdrmtailunamewcwhoami 我从2021年4月份开始才开始真正意义上接触Linux&#xff0c;最初学习时是一脸蒙圈&#xff0c;啥也不会&#xff0c;啥也不懂&#xff0c;做了很多乱七八糟&#xff0c;没有条理的笔记。不知…

NO.3 《机器学习期末复习篇》以题(问答题)促习(人学习),满满干huo,大胆学大胆补!

目录 &#x1f50d; 1. 对于非齐次线性模型 &#xff0c;试将其表示为齐次线性模型形式。 ​编辑 &#x1f50d; 2. 某汽车公司一年内各月份的广告投入与月销量数据如表3-28所示&#xff0c;试根据表中数据构造线性回归模型&#xff0c;并使用该模型预测月广告投入为20万元时…

Javascript算法——贪心算法(一)

贪心算法详解&#xff08;JavaScript&#xff09;&#xff08;局部最优->全局最优&#xff09; 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每一步选择中都采取当前状态下的最优选择&#xff08;局部最优&#xff09;的算法设计方法。通过局部最优解的累积&…

【Vue】分享一个快速入门的前端框架以及如何搭建

先上效果图: 登录 菜单: 下载地址: 链接&#xff1a;https://pan.baidu.com/s/1m-ZlBARWU6_2n8jZil_RAQ 提取码&#xff1a;ui20 … 主要是可以自定义设置token,更改后端请求地址较为方便。 应用设置: 登录与token设置: 在这里设置不用登录,可以请求的接口: request.js i…

jdk8升级JDK21(Springboot2.7.18升级Springboot3.4.0)

目录 背景&#xff1a; 一、maven升级 二、代码改造 2.1 javax替换为jakarta 2.2 swagger2升级swagger3相关更新 2.2.1 新增SpringDocConfig配置类 2.2.2 全局代码更新 2.2.3 全局代码替换&#xff08;普通正则替换&#xff09; 2.3 Mybatis Plus升级 2.4 logback.xm…

数据库(3)--针对列的CRUD操作

1.Create 新增 语法&#xff1a; insert into 表名 &#xff08;列名&#xff09;values &#xff08;列&#xff09;... 创建一个学生表用于演示&#xff1a; create table if not exists student( id bigint comment 编号, name varchar(20) comment 姓名 ); 1.1直接增加…

加速科技荣获“浙江省企业研究院”认定

近日&#xff0c;浙江省经济和信息化厅公布“2024年认定&#xff08;备案&#xff09;省级企业研发机构名单”。经过多轮严格评审和公示&#xff0c;加速科技荣获“省企业研究院”认定。这是加速科技继获国家级专精特新“小巨人”企业认定荣誉后的又一里程碑。 “浙江省企业研究…

leetcode:1784. 检查二进制字符串字段(python3解法)

难度&#xff1a;简单 给你一个二进制字符串 s &#xff0c;该字符串 不含前导零 。 如果 s 包含 零个或一个由连续的 1 组成的字段 &#xff0c;返回 true​​​ 。否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;s "1001" 输出&#xff1a;fa…

双向列表的实现(C++)

一.实现思路 主要是一个空间存储一个数值&#xff0c;然后为了索引后面的数据单元和前面的数据单元&#xff0c;所以在每个空间里面还要存储前面和后面数据单元的指针&#xff0c;就形成了每个数据单元 后面就是要管理的是双向列表的头结点和尾节点&#xff0c;方便实现后面的头…

【前端开发常用网站汇总-01】

1、仿mac界面代码截图 https://codeimg.io/?utm_sourceappinn.com 2、可视化大屏汇总(在线Demo) https://www.xiongze.net/viewdata/index.html 3、在线Photoshop(实现简单P图) https://ps.gaoding.com/#/ 4、在线生成ico图标(png转icon文件) https://www.bitbug.net/in…