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

news/2025/1/10 6:49:12 标签: 数据库, java, mybatis-plus, 多租户

前言

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

大纲

在本篇我们可以

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

代码版本:

java">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目录,如下所示:
在这里插入图片描述
新建多数据源配置类,内容如下:

java">@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响应头返回给用户。如下所示:

java">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校验,并从中提取出租户信息,如下所示:

java">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

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

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

剖析

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

java">dataSource.addDataSource(tenantId, druidDataSourceCreator.createDataSource(dataSourceProperty));
java">    /**
     * 添加数据源
     *
     * @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类中有一些方法删除数据源,还有数据源分组功能,这可以用于主主(从,如果业务场景都是只读的话),策略是轮询和随机:
在这里插入图片描述


http://www.niftyadmin.cn/n/5818301.html

相关文章

有关Redis的相关概述

一、Redis概述 1.1 Redis简介 Redis是一个开源的高性能键值对数据库,使用C语言编写,支持多种数据结构,如字符串(String)、列表(List)、哈希(Hash)、集合(Set…

一文讲清计算机中的镜像,以及其在计算机中的作用

一、什么是计算机中的镜像 在计算机中,镜像(Computer Image)是对系统、磁盘、光盘或应用程序的完整复制或备份,它包含了所有的数据、文件系统、配置和应用程序。镜像技术广泛应用于系统备份、恢复、数据迁移、虚拟化以及软件部署…

9.4 visualStudio 2022 配置 cuda 和 torch (c++)

一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客,作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为: 2.vs…

npm i 报错

nodejs中 使用npm install命令时报错 npm err! file C: \user\admin\package.json_package.json 里缺少 description 和 repository 两个n字段。-CSDN博客

【软考】软件设计师

「学习路线」(推荐该顺序学习,按照先易后难排序) 1、上午题—计算机系统(5~6分)[1.8; ] 2、上午题—程序设计语言(固定6分) 3、下午题—试题一(15分) 4、上午题—知识产权…

培训机构Day23

今天开了javaee,这算是java最重要的部分了,这得好好学。 知识点: JavaEE > JakartaEE Java: 1。Java SE:标准版。standard edition。 -----2。Java EE:Enterprise Edition,多出一些包和库。…

NLP中常见的分词算法(BPE、WordPiece、Unigram、SentencePiece)

文章目录 一、基本概念二、传统分词方法2.1 古典分词方法2.2 拆分为单个字符 三、基于子词的分词方法(Subword Tokenization)3.1 主要思想3.2 主流的 Subword 算法3.2 Subword 与 传统分词方法的比较 四、Byte Pair Encoding (BPE)4.1 主要思想4.2 算法过…

git相关操作笔记

git相关操作笔记 1. git init git init 是一个 Git 命令,用于初始化一个新的 Git 仓库。执行该命令后,Git 会在当前目录创建一个 .git 子目录,这是 Git 用来存储所有版本控制信息的地方。 使用方法如下: (1&#xff…