日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Boot集成Mybatis-Plus多租户架构实战

發布時間:2023/12/14 javascript 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Boot集成Mybatis-Plus多租户架构实战 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目前公司產品就是對外企業服務,入職后了解到SaaS模式和私有部署,當我第一次聽到SaaS時,我不是很理解。經過查閱資料,以及在后續研發功能時,不斷的加深了對多租戶的理解。

那么接下來讓我們問自己幾個問題:1.什么是多租戶架構?2.多租戶架構方案以及各自的優缺點?3.多租戶架構的適用場景?

一. 什么是多租戶

多租戶技術或稱多重租賃技術,簡稱SaaS,是一種軟件架構技術,是實現如何在多用戶環境下(多用戶一般是面向企業用戶)共用相同的系統或程序組件,并且可確保各用戶間數據的隔離性。簡單講:在一臺服務器上運行單個應用實例,它為多個租戶(客戶)提供服務。從定義中我們可以理解:多租戶是一種架構,目的是為了讓多用戶環境下使用同一套程序,且保證用戶間數據隔離。那么重點就很淺顯易懂了,多租戶的重點就是同一套程序下實現多用戶數據的隔離。

二. 多租戶架構以及數據隔離方案

多租戶在數據存儲上主要存在三種方案,分別是:

1. 獨立數據庫

即一個租戶一個數據庫,這種方案的用戶數據隔離級別最高,安全性最好,但成本較高。

  • 優點:為不同的租戶提供獨立的數據庫,有助于簡化數據模型的擴展設計,滿足不同租戶的獨特需求;如果出現故障,恢復數據比較簡單。

  • 缺點:增多了數據庫的安裝數量,隨之帶來維護成本和購置成本的增加。

2. 共享數據庫,獨立 Schema

也就是說 共同使用一個數據庫 使用表進行數據隔離多個或所有租戶共享Database,但是每個租戶一個Schema(也可叫做一個user)。底層庫比如是:DB2、ORACLE等,一個數據庫下可以有多個SCHEMA。

  • 優點:為安全性要求較高的租戶提供了一定程度的邏輯數據隔離,并不是完全隔離;每個數據庫可支持更多的租戶數量。

  • 缺點:如果出現故障,數據恢復比較困難,因為恢復數據庫將牽涉到其他租戶的數據;

3. 共享數據庫,共享 Schema,共享數據表

也就是說 共同使用一個數據庫一個表 使用字段進行數據隔離

即租戶共享同一個Database、同一個Schema,但在表中增加TenantID多租戶的數據字段。這是共享程度最高、隔離級別最低的模式。

簡單來講,即每插入一條數據時都需要有一個客戶的標識。這樣才能在同一張表中區分出不同客戶的數據,這也是我們系統目前用到的(tenant_id)

  • 優點:三種方案比較,第三種方案的維護和購置成本最低,允許每個數據庫支持的租戶數量最多。

  • 缺點:隔離級別最低,安全性最低,需要在設計開發時加大對安全的開發量;數據備份和恢復最困難,需要逐表逐條備份和還原。

三.多租戶架構適用場景?

衡量三種模式主要考慮的因素是隔離還是共享。1.成本角度因素隔離性越好,設計和實現的難度和成本越高,初始成本越高。共享性越好,同一運營成本下支持的用戶越多,運營成本越低。

2.安全因素要考慮業務和客戶的安全方面的要求。安全性要求越高,越要傾向于隔離。

3.從租戶數量上考慮主要考慮下面一些因素

  • 系統要支持多少租戶?上百?上千還是上萬?可能的租戶越多,越傾向于共享。

  • 平均每個租戶要存儲數據需要的空間大小。存貯的數據越多,越傾向于隔離。

  • 每個租戶的同時訪問系統的最終用戶數量。需要支持的越多,越傾向于隔離。

  • 是否想針對每一租戶提供附加的服務,例如數據的備份和恢復等。這方面的需求越多, 越傾向于隔離

4.技術儲備共享性越高,對技術的要求越高。

四. 技術實現

技術選型: Mybatis-Plus這里我們選用了第三種方案(共享數據庫,共享 Schema,共享數據表)來實現,也就意味著,每個數據表都需要有一個租戶標識(tenant_id)

現在有數據庫表(user)如下:

字段名字段類型描述
idint(11)主鍵
namevarchar(30)姓名
tenant_idint(11)多租戶id

將tenant_id視為租戶ID,用來隔離租戶與租戶之間的數據,如果要查詢當前租戶的用戶,SQL大致如下:

SELECT * FROM user WHERE tenant_id = 1;

試想一下,除了一些系統共用的表以外,其他租戶相關的表,我們都需要加上AND tenant_id = ?查詢條件,數據表多的情況時就會漏加導致數據泄露。幸虧有mybatis-plus這個插件,可以極為方便的實現多租戶SQL解析器,官方文檔如下:多租戶 SQL 解析器

正式進入主題 環境搭建演示

1. 創建Spring Boot項目

pom文件:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> </parent> <groupId>com.xd</groupId> <artifactId>mybatis-plus-multi-tenancy</artifactId> <version>0.0.1-SNAPSHOT</version> <name>mybatis-plus-multi-tenancy</name> <description>基于Spring Boot Mybatis-Plus的多租戶架構</description> <properties> <java.version>1.8</java.version> <mybatis-plus.version>3.1.2</mybatis-plus.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--Mybatis-Plus依賴--> <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-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.0.M1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>

application.properties

# 數據源配置spring.datasource.type=com.zaxxer.hikari.HikariDataSourcespring.datasource.hikari.minimum-idle=3spring.datasource.hikari.maximum-pool-size=10# 不能小于30秒,否則默認回到1800秒spring.datasource.hikari.max-lifetime=30000spring.datasource.hikari.connection-test-query=SELECT 1spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/multi?useUnicode=true&characterEncoding=UTF-8spring.datasource.username=rootspring.datasource.password=rootlogging.level.com.xd.mybatisplusmultitenancy=debug

對應的SQL數據庫初始化schema文件

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for user-- ----------------------------DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `tenant_id` int(11) NOT NULL COMMENT '多租戶ID', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;SET FOREIGN_KEY_CHECKS = 1;

MybatisPlusConfig核心配置:TenantSqlParser多租戶處理器

package com.xd.mybatisplusmultitenancy.config;import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;import lombok.extern.slf4j.Slf4j;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.LongValue;import net.sf.jsqlparser.expression.NullValue;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.List;/** * @Classname PreTenantHandler * @Description 租戶處理器 -主要實現mybatis-plus https://mp.baomidou.com/guide/tenant.html * @Author Created by Lihaodong (alias:小東啊) lihaodongmail@163.com * @Date 2019-08-09 23:34 * @Version 1.0 */@Slf4j@Componentpublic class MyTenantHandler implements TenantHandler { /** * 多租戶標識 */ private static final String SYSTEM_TENANT_ID = "tenant_id"; /** * 需要過濾的表 */ private static final List<String> IGNORE_TENANT_TABLES = new ArrayList<>(); @Autowired private MyContext apiContext; /** * 租戶Id * * @return */ @Override public Expression getTenantId() { // 從當前系統上下文中取出當前請求的服務商ID,通過解析器注入到SQL中。 Long tenantId = apiContext.getCurrentTenantId(); log.debug("當前租戶為{}", tenantId); if (tenantId == null) { return new NullValue(); } return new LongValue(tenantId); } /** * 租戶字段名 * * @return */ @Override public String getTenantIdColumn() { return SYSTEM_TENANT_ID; } /** * 根據表名判斷是否進行過濾 * 忽略掉一些表:如租戶表(sys_tenant)本身不需要執行這樣的處理 * * @param tableName * @return */ @Override public boolean doTableFilter(String tableName) { return IGNORE_TENANT_TABLES.stream().anyMatch((e) -> e.equalsIgnoreCase(tableName)); }}

MybatisPlus的配置

package com.xd.mybatisplusmultitenancy.config;import com.baomidou.mybatisplus.core.parser.ISqlParser;import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.LongValue;import java.util.ArrayList;import java.util.List;/** * @Classname MybatisPlusConfig * @Description TODO * @Author Created by Lihaodong (alias:小東啊) lihaodongmail@163.com * @Date 2019-08-09 22:44 * @Version 1.0 */@Configuration@MapperScan("com.xd.mybatisplusmultitenancy.mapper")public class MybatisPlusConfig { @Autowired private MyTenantHandler myTenantHandler; @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // SQL解析處理攔截:增加租戶處理回調。 List<ISqlParser> sqlParserList = new ArrayList<>(); // 攻擊 SQL 阻斷解析器、加入解析鏈 sqlParserList.add(new BlockAttackSqlParser()); // 多租戶攔截 TenantSqlParser tenantSqlParser = new TenantSqlParser(); tenantSqlParser.setTenantHandler(myTenantHandler); sqlParserList.add(tenantSqlParser); paginationInterceptor.setSqlParserList(sqlParserList); return paginationInterceptor; } /** * 性能分析攔截器,不建議生產使用 * 用來觀察 SQL 執行情況及執行時長 */ @Bean(name = "performanceInterceptor") public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); }}

自定義系統的上下文

package com.xd.mybatisplusmultitenancy.config;import org.springframework.stereotype.Component;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * @Classname ApiContext * @Description 當前系統的上下文 * @Author Created by Lihaodong (alias:小東啊) lihaodongmail@163.com * @Date 2019-08-09 22:47 * @Version 1.0 */@Componentpublic class MyContext { private static final String KEY_CURRENT_TENANT_ID = "KEY_CURRENT_PROVIDER_ID"; private static final Map<String, Object> M_CONTEXT = new ConcurrentHashMap<>(); public void setCurrentTenantId(Long tenantId) { M_CONTEXT.put(KEY_CURRENT_TENANT_ID, tenantId); } public Long getCurrentTenantId() { return (Long) M_CONTEXT.get(KEY_CURRENT_TENANT_ID); }}

Entity、Mapper 省略...

2. 單元測試

package com.xd.mybatisplusmultitenancy.test;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import com.xd.mybatisplusmultitenancy.MybatisPlusMultiTenancyApplication;import com.xd.mybatisplusmultitenancy.config.MyContext;import com.xd.mybatisplusmultitenancy.entity.User;import com.xd.mybatisplusmultitenancy.mapper.UserMapper;import lombok.extern.slf4j.Slf4j;import org.junit.Assert;import org.junit.Before;import org.junit.FixMethodOrder;import org.junit.Test;import org.junit.runner.RunWith;import org.junit.runners.MethodSorters;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.sql.Wrapper;/** * @Classname MybatisPlusMultiTenancyApplicationTests * @Description TODO * @Author Created by Lihaodong (alias:小東啊) lihaodongmail@163.com * @Date 2019-08-09 22:50 * @Version 1.0 */@Slf4j@RunWith(SpringRunner.class)@FixMethodOrder(MethodSorters.JVM)@SpringBootTest(classes = MybatisPlusMultiTenancyApplication.class)public class MybatisPlusMultiTenancyApplicationTests { @Autowired private MyContext apiContext; @Autowired private UserMapper userMapper; /** * 模擬當前系統的多租戶Id */ @Before public void before() { // 在上下文中設置當前多租戶id apiContext.setCurrentTenantId(1L); } @Test public void insert() { // 新增數據 User user = new User().setName("小明"); //判斷一個條件是true還是false Assert.assertTrue(userMapper.insert(user) > 0); user = userMapper.selectById(user.getId()); log.info("插入數據:{}", user); // 判斷是否相等 Assert.assertEquals(apiContext.getCurrentTenantId(), user.getTenantId()); } @Test public void selectList() { userMapper.selectList(null).forEach((e) -> { log.info("查詢數據{}", e); Assert.assertEquals(apiContext.getCurrentTenantId(), e.getTenantId()); }); }}

運行結果插入數據

2019-08-23 22:32:52.755 INFO 77902 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'2019-08-23 22:32:53.210 INFO 77902 --- [ main] .MybatisPlusMultiTenancyApplicationTests : Started MybatisPlusMultiTenancyApplicationTests in 5.181 seconds (JVM running for 6.86)2019-08-23 22:32:53.613 DEBUG 77902 --- [ main] c.x.m.config.MyTenantHandler : 當前租戶為12019-08-23 22:32:53.614 DEBUG 77902 --- [ main] c.x.m.mapper.UserMapper.insert : ==> Preparing: INSERT INTO user (name, tenant_id) VALUES (?, 1) 2019-08-23 22:32:53.648 DEBUG 77902 --- [ main] c.x.m.mapper.UserMapper.insert : ==> Parameters: 小明(String)2019-08-23 22:32:53.701 DEBUG 77902 --- [ main] c.x.m.mapper.UserMapper.insert : <== Updates: 1 Time:64 ms - ID:com.xd.mybatisplusmultitenancy.mapper.UserMapper.insertExecute SQL:INSERT INTO user (name, tenant_id) VALUES ('小明', 1)2019-08-23 22:32:53.720 DEBUG 77902 --- [ main] c.x.m.config.MyTenantHandler : 當前租戶為12019-08-23 22:32:53.722 DEBUG 77902 --- [ main] c.x.m.mapper.UserMapper.selectById : ==> Preparing: SELECT id, name, tenant_id FROM user WHERE user.tenant_id = 1 AND id = ? 2019-08-23 22:32:53.726 DEBUG 77902 --- [ main] c.x.m.mapper.UserMapper.selectById : ==> Parameters: 1(Long)2019-08-23 22:32:53.745 DEBUG 77902 --- [ main] c.x.m.mapper.UserMapper.selectById : <== Total: 1 Time:20 ms - ID:com.xd.mybatisplusmultitenancy.mapper.UserMapper.selectByIdExecute SQL:SELECT id, name, tenant_id FROM user WHERE user.tenant_id = 1 AND id = 12019-08-23 22:32:53.746 INFO 77902 --- [ main] .MybatisPlusMultiTenancyApplicationTests : 插入數據:User(id=1, name=小明, tenantId=1)2019-08-23 22:32:53.762 INFO 77902 --- [ Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'2019-08-23 22:32:53.764 INFO 77902 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...2019-08-23 22:32:53.777 INFO 77902 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.

查詢數據

2019-08-23 22:34:26.700 INFO 77922 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'2019-08-23 22:34:27.100 INFO 77922 --- [ main] .MybatisPlusMultiTenancyApplicationTests : Started MybatisPlusMultiTenancyApplicationTests in 4.521 seconds (JVM running for 6.268)2019-08-23 22:34:27.412 DEBUG 77922 --- [ main] c.x.m.config.MyTenantHandler : 當前租戶為12019-08-23 22:34:27.414 DEBUG 77922 --- [ main] c.x.m.mapper.UserMapper.selectList : ==> Preparing: SELECT id, name, tenant_id FROM user WHERE user.tenant_id = 1 2019-08-23 22:34:27.442 DEBUG 77922 --- [ main] c.x.m.mapper.UserMapper.selectList : ==> Parameters: 2019-08-23 22:34:27.464 DEBUG 77922 --- [ main] c.x.m.mapper.UserMapper.selectList : <== Total: 1 Time:22 ms - ID:com.xd.mybatisplusmultitenancy.mapper.UserMapper.selectListExecute SQL:SELECT id, name, tenant_id FROM user WHERE user.tenant_id = 12019-08-23 22:34:27.467 INFO 77922 --- [ main] .MybatisPlusMultiTenancyApplicationTests : 查詢數據User(id=1, name=小明, tenantId=1)2019-08-23 22:34:27.480 INFO 77922 --- [ Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'2019-08-23 22:34:27.482 INFO 77922 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...2019-08-23 22:34:27.492 INFO 77922 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.

從打印的日志不難看出,目前這個方案還是比較完美的,僅需簡單的配置,讓開發者極大方便的進行開發,同時又最大程度的保證了數據的安全性

源碼下載:?https://github.com/LiHaodong888/SpringBootLearn

具體項目:https://gitee.com/li_haodong/pre

參考資料

https://www.cnblogs.com/pingfan21/p/7478242.html https://segmentfault.com/a/1190000017197768

總結

以上是生活随笔為你收集整理的Spring Boot集成Mybatis-Plus多租户架构实战的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。