SaaS化多租户实现的两种方法

SaaS化多租户实现的两种方法

SaaS系统的定义 SaaS,全称为Software-as-a-Service(软件即服务),是一种基于云计算的软件交付模式。而SaaS系统,即是通过这种模式提供给用户的软件系统。即多租户系统,每个租户独立,只能看到自己数据。

一、租户id隔离

这种方法比较简单,在每张表里添加一个字段tenant_id,给每个企业(租户)一个唯一tenant_id,那么在SQL的一切增删改查都带上tenant_id,即可实现租户隔离。

如何自动带上租户id,无需每次都在sql上添加tenant_id?
使用mybatis-plugin可以做到

步骤一、写一个拦截器

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class})
})
public class CustomerInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {//todo 拦截逻辑System.out.println("");StatementHandler statementHandler = (StatementHandler) invocation.getTarget();String originalSql = statementHandler.getBoundSql().getSql();//实际开发中从登录用户去获取他的tenant_id String modifiedSql = originalSql + " AND tenant_id = '" + tenant_id + "'";ReflectUtil.setFieldValue(statementHandler.getBoundSql(), "sql", modifiedSql);return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target,this);}@Overridepublic void setProperties(Properties properties) {//设置属性}
}

步骤二、注册插件

@Configuration
public class MybatisConfig {@Beanpublic String myInterceptor(SqlSessionFactory sqlSessionFactory) {sqlSessionFactory.getConfiguration().addInterceptor(new CustomerInterceptor());return "interceptor";}
}

二、动态数据源(重点)

本文重点要讲的是使用动态数据源实现动态切换数据库,来实现多租户自由切换
本文使用的是mybatis-flex

步骤一、注册租户和数据源到数据库

即,把租户的唯一信息和分配给租户的数据源一一对应,存入数据库,例如:

CREATE TABLE `datasource` (`id` bigint NOT NULL AUTO_INCREMENT,`display_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '显示名称',`db_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '链接默认数据库',`schema_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '数据库schema',`pool_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '连接池名称必须唯一',`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '描述',`db_host` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据库地址',`db_port` int NOT NULL COMMENT '数据库端口',`db_user` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户',`db_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',`db_driver` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '驱动',`connect_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '连接参数',`create_time` timestamp NULL DEFAULT NULL,`update_time` timestamp NULL DEFAULT NULL,`create_by` timestamp NULL DEFAULT NULL,`update_by` timestamp NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `datasource_pool_name_uindex` (`pool_name`) USING BTREE,KEY `database_creator_id_index` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=159 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

示例数据:
在这里插入图片描述

步骤二、程序启动完成把数据连接信息加载到JVM
@Component
@Order(1)
public class InitialDataSource implements CommandLineRunner {public static final String DATASOURCE_MYSQL_COMMON_PARAMS_URL = "jdbc:%s://%s:%s/%s?%s";public static final String DATASOURCE_PGSQL_COMMON_PARAMS_URL = "jdbc:%s://%s:%s/%s?%s&%s";//这是数据源(步骤1提到的)表的mapper接口@Resourceprivate DatasourceMapper datasourceMapper;@Overridepublic void run(String... args) throws Exception {//1.清空内存中的数据源DataSourceKey.clear();//2.把数据库的datasource查询出来List<Datasource> fillSubmittals = datasourceMapper.selectAll();//3.动态添加新的数据源 FlexDataSource来自于mybatis-flexFlexDataSource flexDataSource = FlexGlobalConfig.getDefaultConfig().getDataSource();fillSubmittals.forEach(item -> addDatasourceItem(flexDataSource, item));}/*** 组装-添加数据源** @param flexDataSource* @param item*/public void addDatasourceItem(FlexDataSource flexDataSource, Datasource item) {DruidDataSource druidDataSource = buildDruidDataSource(item);//数据源信息加载到内存addIntoJVMDynamicPool(flexDataSource, item.getPoolName(),druidDataSource);}/*** 数据源信息加载到内存* @param flexDataSource* @param poolName* @param druidDataSource* @return*/public void addIntoJVMDynamicPool(FlexDataSource flexDataSource, String poolName, DruidDataSource druidDataSource){flexDataSource.addDataSource(poolName,druidDataSource);}public DruidDataSource buildDruidDataSource(Datasource item){DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl(getCommonUrl(item));druidDataSource.setDriverClassName(DriverEnum.findByEnumDescription(item.getDbDriver()).getDriverClass());druidDataSource.setUsername(item.getDbUser());druidDataSource.setPassword(item.getDbPassword());druidDataSource.setValidationQuery("select 1");return druidDataSource;}public String getCommonUrl(Datasource datasource){String url = null;if(DriverEnum.MYSQL.getDescription().equals(datasource.getDbDriver())){url = String.format(DATASOURCE_MYSQL_COMMON_PARAMS_URL,datasource.getDbDriver(),datasource.getDbHost(),datasource.getDbPort(),StringUtils.hasText(datasource.getDbName()) ? datasource.getDbName() : "",StringUtils.hasText(datasource.getConnectParams()) ? datasource.getConnectParams() : "");}else if(DriverEnum.POSTGRES.getDescription().equals(datasource.getDbDriver())){url =String.format(DATASOURCE_PGSQL_COMMON_PARAMS_URL,datasource.getDbDriver(),datasource.getDbHost(),datasource.getDbPort(),StringUtils.hasText(datasource.getDbName()) ? datasource.getDbName() : "",StringUtils.hasText(datasource.getSchemaName()) ? "currentSchema="+datasource.getSchemaName() : "",StringUtils.hasText(datasource.getConnectParams()) ? datasource.getConnectParams() : "");}return url;}}

用到的枚举:

@Getter
public enum DriverEnum {/*** pg*/POSTGRES(0, "postgresql", "org.postgresql.Driver", DbType.postgresql, "postgres"),/*** mysql*/MYSQL(1, "mysql", "com.mysql.cj.jdbc.Driver", DbType.mysql,"mysql"),/*** ck*/CLICK_HOUSE(2, "clickhouse", "com.clickhouse.jdbc.ClickHouseDriver", DbType.clickhouse, "clickhouse");private final int index;// bi 记录驱动private final String description;private final String driverClass;// metabase 记录驱动private final String engine;private final DbType analysisType;DriverEnum(int index, String description, String driverClass, DbType analysisType, String engine){this.index = index;this.description = description;this.driverClass = driverClass;this.analysisType = analysisType;this.engine = engine;}private static final Map<String, DriverEnum> DESCRIPTION_ENUMS_MAP = Map.of(DriverEnum.POSTGRES.getDescription(), DriverEnum.POSTGRES,DriverEnum.MYSQL.getDescription(), DriverEnum.MYSQL,DriverEnum.CLICK_HOUSE.getDescription(), DriverEnum.CLICK_HOUSE);private static final Map<String, DbType> ANALYSISTYPE_ENUMS_MAP = Map.of(DriverEnum.POSTGRES.getDescription(), DbType.postgresql,DriverEnum.MYSQL.getDescription(), DbType.mysql,DriverEnum.CLICK_HOUSE.getDescription(), DbType.clickhouse);/*** 判断参数合法性*/public static boolean isValidName(String name) {for (DriverEnum cardStatus : DriverEnum.values()) {if (cardStatus.getDescription().equals(name)) {return true;}}return false;}/*** 根据描述查找枚举* @param description 描述* @return 枚举*/public static DriverEnum findByEnumDescription(String description){return DESCRIPTION_ENUMS_MAP.getOrDefault(description, DriverEnum.POSTGRES);}/*** 根据描述查找SQL解析器* @param description 描述* @return 枚举*/public static DbType findAnalysisTypeByDescription(String description){return ANALYSISTYPE_ENUMS_MAP.getOrDefault(description, DbType.postgresql);}
}
步骤三、业务使用(只列出核心)

@Resourceprivate JdbcTemplate jdbcTemplate;public List<FillSubmittal> queryAll() {String sql1 = "select * from datasource";//设置数据库pool-name   与步骤一的表里的pool_name对应DataSourceKey.use("c-1");executeSql(sql1);String sql2 = "select * from nc_fill_table_24_4lg0aa20f4rw9r";DataSourceKey.use("c-2");executeSql(sql2);String sql3 = "select * from user_info";DataSourceKey.use("c-3");executeSql(sql3);String sql4 = "select * from sys_role";DataSourceKey.use("c-4");executeSql(sql4);return null;}
 public void executeSql(String sql){List<Map<String, Object>> list =jdbcTemplate.queryForList(sql);log.info(JSON.toJSONString(list));}

这样,每个租户注册的时候就分配一个数据源,在使用时,根据租户灯笼裤信息获取到他对应的数据源信息,就可以通过DataSourceKey.use设置当前租户要用的数据源,实现动态切换


如图: 这里只画了程序启动,把数据加载到JVM中,和使用时,根据不用的pool_name(与租户一一对应)切换到对应的数据源。那么执行sql得到的就是对应数据源的数据,注意:这里执行sql使用的是jdbcTemplate了

在这里插入图片描述

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

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

相关文章

腾讯云升级多个云存储解决方案 以智能化存储助力企业增长

9月6日&#xff0c;在腾讯数字生态大会腾讯云储存专场上&#xff0c;腾讯云升级多个存储解决方案&#xff1a;Data Platform 数据平台解决方案重磅发布&#xff0c;数据加速器 GooseFS、数据处理平台数据万象、日志服务 CLS、高性能并行文件存储 CFS Turbo 等多产品全新升级&am…

TypeScript 扩展

扩展 ?:可选参数 可选链事实上并不是TypeScript独有的特性&#xff0c;它是ES11&#xff08;ES2020&#xff09;中增加的特性 可选链使用可选链操作符 ? 作用是当对象的属性不存在时&#xff0c;会短路&#xff0c;直接返回undefined&#xff0c;如果存在&#xff0c;那么…

SpringCloud集成ELK

1、添加依赖 <dependency><groupId>net.logstash.logback</groupId><artifactId>logstash-logback-encoder</artifactId><version>6.1</version> </dependency>2、在logback-spring.xml中添加配置信息&#xff08;logback-sp…

Android SystemUI组件(06)导航栏创建分析虚拟按键

该系列文章总纲链接&#xff1a;专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节持续迭代之前章节的思维导图&#xff0c;主要关注左侧SystemBars分析中导航栏部分即可。 1 导航栏创建之makeStatusBarView 通过上一篇文章的…

前端 + 接口请求实现 vue 动态路由

前端 接口请求实现 vue 动态路由 在 Vue 应用中&#xff0c;通过前端结合后端接口请求来实现动态路由是一种常见且有效的权限控制方案。这种方法允许前端根据用户的角色和权限&#xff0c;动态生成和加载路由&#xff0c;而不是在应用启动时就固定所有的路由配置。 实现原理…

el-tree父子不互相关联时,手动实现全选、反选、子级全选、清空功能

el-tree父子不互相关联时&#xff0c;手动实现全选、反选、子级全选、清空功能 1、功能实现图示 2、实现思路 当属性check-strictly为true时&#xff0c;父子节点不互相关联&#xff0c;如果需要全部选中或选择某一节点下的全部节点就必须手动选择每个节点&#xff0c;十分麻…

【mysql】逻辑运算符

逻辑运算符 逻辑运算符主要是为了判断表达式的真假,返回结果也是1,0,null OR 这里面或就是两个条件或的关系,比如我要department_id等于10和等于20的情况就可以使用或. SELECT last_name,salary,department_id FROM employees WHERE department_id10 OR department_id20 …

CTF——简单的《WEB》

文章目录 一、WEB1、easysql2、baby_web3、baby_sql4、upload_easy5、easygame拓展1.1拓展1.2 6、ht_ssti7、包容乃大 一、WEB 1、easysql 题目描述&#xff1a; sql注入漏洞 1.常用的sql注入测试语句 2.sql注入bypass 解题思路 这边提示基本给的也很完整的&#xff0c;不…

C++开发基础之理解 CUDA 编译配置:`compute_XX` 和 `sm_XX` 的作用

前言 在 CUDA 编程中&#xff0c;确保代码能够在不同的 NVIDIA GPU 上高效运行是非常重要的。为了实现这一点&#xff0c;CUDA 编译器 (nvcc) 提供了多种配置选项&#xff0c;其中 compute_XX 和 sm_XX 是两个关键的编译选项。本文将深入探讨这两个选项的作用及其配置顺序&…

大一新生以此篇开启你的算法之路

各位大一计算机萌新们&#xff0c;你们好&#xff0c;本篇博客会带领大家进行算法入门&#xff0c;给各位大一萌新答疑解惑。博客文章略长&#xff0c;可根据自己的需要观看&#xff0c;在博客中会有给大一萌新问题的解答&#xff0c;请不要错过。 入门简介&#xff1a; 算法…

可信的人类与人工智能协作:基于人类反馈和物理知识的安全自主驾驶强化学习

可信的人类与人工智能协作&#xff1a;基于人类反馈和物理知识的安全自主驾驶强化学习 Abstract 在自动驾驶领域&#xff0c;开发安全且可信赖的自动驾驶策略仍然是一项重大挑战。近年来&#xff0c;结合人类反馈的强化学习&#xff08;RLHF&#xff09;因其提升训练安全性和…

中国银河资产笔试25届考什么?如何通过考试|附真题库面试攻略

嘿&#xff0c;各位小伙伴们&#xff01;我是职小豚&#xff0c;今天就带大家一起探秘中国银河资产 25 届秋招&#xff0c;为大家揭开这场金融之旅的神秘面纱。 一、中国银河资产介绍 中国银河资产&#xff0c;那可是金融领域的璀璨巨星&#xff01;它就像一座闪耀着智慧光芒…

unity安装配置和vs2022联动教程

目录 1.选择vs2022配置 2.安装unity 2.1安装unity hub 2.2注册个人账号 2.3安装编辑器 2.4修改为简体中文 2.5添加许可证 2.6安装位置修改 3.项目的创建 3.1如何创建 3.2如何选择 3.3配置语言 3.4去哪里找语言包 4.unity编辑器窗口的介绍 4.1游戏的运行和停止 4…

11、Hive+Spark数仓环境准备

1、 Hive安装部署 1&#xff09;把hive-3.1.3.tar.gz上传到linux的/opt/software目录下 2&#xff09;解压hive-3.1.3.tar.gz到/opt/module/目录下面 [shuidihadoop102 module]$ tar -zxvf /opt/software/hive-3.1.3.tar.gz -C /opt/module/ 3&#xff09;修改hive-3.1.3-b…

《深度学习》深度学习 框架、流程解析、动态展示及推导

目录 一、深度学习 1、什么是深度学习 2、特点 3、神经网络构造 1&#xff09;单层神经元 • 推导 • 示例 2&#xff09;多层神经网络 3&#xff09;小结 4、感知器 神经网络的本质 5、多层感知器 6、动态图像示例 1&#xff09;一个神经元 相当于下列状态&…

安卓开发板_联发科MTK开发评估套件串口调试

串口调试 如果正在进行lk(little kernel ) 或内核开发&#xff0c;USB 串口适配器&#xff08; USB 转串口 TTL 适配器的简称&#xff09;对于检查系统启动日志非常有用&#xff0c;特别是在没有图形桌面显示的情况下。 1.选购适配器 常用的许多 USB 转串口的适配器&#xf…

通过nginx代理转发实现共用80和443端口

目录 项目场景&#xff1a; 问题&#xff1a;怎么实现端口共用&#xff1f; 一、域名解析到nginx服务器 二、创建vhost解析到实际的服务器 三、防火墙的配置 项目场景&#xff1a; 公司自建服务器&#xff0c;有一条专线带有公网IP&#xff0c;如何满足不同的域名解析共用…

go-map系统学习

map底层结构 Goland的map的底层结构使用hash实现&#xff0c;一个hash表里有多个hash表节点&#xff0c;即bucket&#xff0c;每个bucket保存了map中的一个或者一组键值对。 map结构定义&#xff1a; runtime/map.go:hmap type hmap struct {// Note: the format of the hma…

win10 安装多个版本的python

1&#xff0c;安装python3.9 和python3.10 2, 安装完之后分别打开两个版本的Python的安装目录&#xff08;第一层目录&#xff09;&#xff0c;把pythonw.exe分别重命名为pythonw_39.exe和pythonw_310.exe&#xff0c;把python.exe复制一份&#xff0c;并分别重命名为python_…

【C++】_stack和_queue容器适配器、_deque

当别人都在关注你飞的有多高的时候&#xff0c;只有父母在关心你飞的累不累。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;stack •&#x1f330;1.stack介绍 •&#x1f330;2.stack的基本操作 &#x1f34b;知识点二&…