spring結合mybatis多租戶實現單庫分表

實現單庫分表

思路:student表數據量大,所以將其進行分表處理。一共有三個分表,分別是student0,student1,student2,在新增數據的時候,根據請求頭中的meta-tenant參數決定數據存在哪張表表。

數據庫

1. 建立數據庫study1

2. 在數據庫中建表,分別為student,student0,student1,student2,四個表結構都一樣,只是表名不一樣

CREATE TABLE `student` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(50) DEFAULT NULL,`age` int DEFAULT NULL,`credit` varchar(14) DEFAULT NULL,`tenant_id` int DEFAULT NULL COMMENT '租戶id',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

引入pom

<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc-core-spring-boot-starter -->
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId><version>5.1.1</version>
</dependency>

配置文件application.yml

#單庫分表
spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource:names: ds1ds1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/study1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8username: rootpassword: root1234mode:type: Memoryoverwrite: truerules:sharding:tables:student:actual-data-nodes: ds1.student$->{0..2}key-generate-strategy:column: idkey-generator-name: snowflakebinding-tables:- studentdefault-table-strategy:standard:sharding-algorithm-name: custom_inlinesharding-column: tenant_iddefault-sharding-column: tenant_idsharding-algorithms:custom_inline:type: CLASS_BASEDprops:strategy: STANDARDalgorithmClassName: com.cyy.config.TablePreciseShardingAlgorithmprops:sql-show: true
tenant:enable: true #啟用多租戶column: tenant_id

實現標準分片算法接口

package com.cyy.config;import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.springframework.stereotype.Component;import java.util.Collection;/*** 標準分表算法*/
@Component
public class TablePreciseShardingAlgorithm implements StandardShardingAlgorithm<Integer> {@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Integer> preciseShardingValue) {String tableName = preciseShardingValue.getLogicTableName().toLowerCase() + (preciseShardingValue.getValue() %10);return tableName;}@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Integer> rangeShardingValue) {return collection;}@Overridepublic void init() {}@Overridepublic String getType() {return null;}
}

配置租戶上下文相關內容

用到的常量
package com.cyy.constant;/*** 多租戶相關的常量*/
public class TenantConstant {public static final String META_TENANT_ID = "meta-tenant";public static final String META_TENANT_ID_PARAM = "tenantId";public static final Long TENANT_ID_DEFAULT = 1l;
}
定義租戶上下文
package com.cyy.config;/*** 定義租戶上下文,通過上下文保存當前租戶的信息*/
public class TenantContextHolder {private static final ThreadLocal<Long> CONETXT_HOLDER = new ThreadLocal<>();public static void set(Long l){CONETXT_HOLDER.set(l);}public static Long getTenantId(){return CONETXT_HOLDER.get();}public static void remove(){CONETXT_HOLDER.remove();}
}
設置多租戶的相關的屬性
package com.cyy.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.HashSet;
import java.util.Set;@Data
/*** 將配置文件中的屬性自動映射都Java對象的字段中* 指定配置文件中的配置項的前綴為tenant,簡化@Vlue注解,不需要加很多的@Value*/
@ConfigurationProperties(prefix = "tenant")
@Component
public class TenantProperties {//不需要加tenantid的mapper方法名public static Set<String> NOT_PROCEED = new HashSet<>();//不需要加tenentid的表名public static Set<String> NOT_TABLES = new HashSet<>();//是否開啟多租戶,讀取的是配置文件中的tenant.enable的值,等價于@Value(${tenant.enable})private boolean enable = false;//多租戶字段private String column = "tenant_id";
}
配置租戶上下文攔截器
package com.cyy.interceptor;import com.cyy.config.TenantContextHolder;
import com.cyy.config.TenantProperties;
import com.cyy.constant.TenantConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** HandlerInterceptor是在handler處理請求之前或者之后執行的攔截器,可以對請求做預處理或者對響應結果做統一處理,實現日志記錄或者權限認證等功能* HandlerInterceptor可以攔截所有的請求,也可以只攔截特定的親故*/
@Slf4j
@Component
public class TenantContextHandlerInterceptor implements HandlerInterceptor {@Resourceprivate TenantProperties tenantProperties;public TenantContextHandlerInterceptor(){}/*** 在請求處理前設置租戶上下文* @param request current HTTP request* @param response current HTTP response* @param handler chosen handler to execute, for type and/or instance evaluation* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("=====對請求進行統一攔截=====");if (tenantProperties.isEnable()){//從請求頭中獲取tenantIdString tenantId = request.getHeader(TenantConstant.META_TENANT_ID);if (StringUtils.isNotBlank(tenantId)){TenantContextHolder.set(Long.parseLong(tenantId));} else {TenantContextHolder.set(TenantConstant.TENANT_ID_DEFAULT);}log.info("獲取到的租戶id為:【{}】",TenantContextHolder.getTenantId());}return true;}/*** 請求處理完成之后的回調* @param request current HTTP request* @param response current HTTP response* @param handler handler (or {@link HandlerMethod}) that started asynchronous* execution, for type and/or instance examination* @param ex exception thrown on handler execution, if any* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {TenantContextHolder.remove();}
}
將租戶上下文攔截器添加到springmvc配置中

對服務器的所有請求進行攔截,從請求頭的meta-tenant參數中獲取tenant_id值,并設置租戶id

package com.cyy.config;import com.cyy.interceptor.TenantContextHandlerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;
import java.util.List;/*** 通過實現WebMvcConfigurer接口,可以自定義springmvc的配置,例如添加攔截器*/
@Configuration
public class CustomeConfig implements WebMvcConfigurer {@Resourceprivate MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;/*** TenantContextHandlerInterceptor類本身就是一個Bean,在這塊再次聲明一個相同Bean的時候,會報錯* 可以在配置文件中添加配置allow-bean-definition-overriding: true,允許bean定義覆蓋* @return*/@Bean("tenantContextHandlerInterceptor")public HandlerInterceptor customerInterceptor(){return new TenantContextHandlerInterceptor();}/*** 添加攔截器* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(customerInterceptor());}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(0,mappingJackson2HttpMessageConverter);}
}

配置mybatis的插件

繼承多租戶攔截器TenantLineInnerInterceptor
package com.cyy.interceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.cyy.config.TenantContextHolder;
import com.cyy.config.TenantProperties;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.sql.SQLException;
import java.util.Objects;/*** 繼承多租戶攔截器*/
public class CustomTenantLineInnerInterceptor extends TenantLineInnerInterceptor {private TenantProperties tenantProperties;public TenantProperties getTenantProperties(){return tenantProperties;}public void setTenantProperties(TenantProperties tenantProperties){this.tenantProperties = tenantProperties;}@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {if (TenantProperties.NOT_PROCEED.stream().anyMatch(s -> s.equalsIgnoreCase(ms.getId()))){return;}if (Objects.isNull(TenantContextHolder.getTenantId())){return;}super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);}public CustomTenantLineInnerInterceptor(final TenantProperties tenantProperties, final TenantLineHandler tenantLineHandler){super(tenantLineHandler);this.setTenantProperties(tenantProperties);this.setTenantLineHandler(tenantLineHandler);}
}
添加多租戶攔截器到mybatisplus攔截器中

將多租戶攔截器CustomTenantLineInnerInterceptor添加到MybatisPlusInterceptor的攔截器中

package com.cyy.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.cyy.interceptor.CustomTenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;
import java.util.Objects;@Configuration
@MapperScan("com.cyy.mapper")
public class MybatisPlusConfig {@Resourceprivate TenantProperties tenantProperties;@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//添加多租戶攔截器if (tenantProperties.isEnable()){mybatisPlusInterceptor.addInnerInterceptor(tenantLineInnerInterceptor());}//分頁插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//防止全表更新與刪除插件mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());//樂觀鎖插件mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mybatisPlusInterceptor;}public TenantLineInnerInterceptor tenantLineInnerInterceptor(){return new CustomTenantLineInnerInterceptor(tenantProperties, new TenantLineHandler() {/*** 獲取租戶id* @return*/@Overridepublic Expression getTenantId() {Long tenantId = TenantContextHolder.getTenantId();if (Objects.nonNull(tenantId)){return new LongValue(tenantId);}return null;}/*** 獲取多租戶的字段名* @return*/@Overridepublic String getTenantIdColumn() {return tenantProperties.getColumn();}/*** 根據表名判斷是否忽略拼接多租戶條件* @param tableName 表名* @return*/@Overridepublic boolean ignoreTable(String tableName) {return tenantProperties.NOT_TABLES.stream().anyMatch((t) -> t.equalsIgnoreCase(tableName));}});}
}

測試代碼?

測試數據插入的controller

package com.cyy.controller;import com.cyy.domain.Student;
import com.cyy.service.StudentService;
import com.cyy.util.RoundRobinUtil;
import com.cyy.config.TenantContextHolder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** 學生類控制器*/
@RestController
@RequestMapping("/student")
public class StudentController {@Resourceprivate StudentService studentService;/*** 多租戶測試-插入一個用戶*/@PostMapping("/insert")public ResponseEntity insert(@RequestBody Student student){student.setTenantId(TenantContextHolder.getTenantId().intValue());int i = studentService.insert(student);if (i > 0){return ResponseEntity.ok("插入成功");}return ResponseEntity.ok("插入失敗");}
}

請求參數

{"id": 3,"name": "孫三","age": 3,"credit": "3"
}

請求頭

運行結果

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70967.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70967.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70967.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Ecode前后端傳值

說明 在泛微 E9 系統開發過程中&#xff0c;使用 Ecode 調用后端接口并進行傳值是極為常見且關鍵的操作。在上一篇文章中&#xff0c;我們探討了 Ecode 調用后端代碼的相關內容&#xff0c;本文將深入剖析在 Ecode 中如何向后端傳值&#xff0c;以及后端又該如何處理接收這些值…

黑馬Java面試教程_P5_微服務

系列博客目錄 文章目錄 系列博客目錄1.引言2.Spring Cloud2.1 Spring Cloud 5大組件有哪些?面試文稿 2.2 服務注冊和發現是什么意思?Spring Cloud 如何實現服務注冊發現?面試文稿 2.3 我看你之前也用過nacos、你能說下nacos與eureka的區別?面試文稿 2.4 你們項目負載均衡如…

【2025深度學習環境搭建-2】pytorch+Docker+VS Code+DevContainer搭建本地深度學習環境

上一篇文章&#xff1a;【2025深度學習環境搭建-1】在Win11上用WSL2和Docker解鎖GPU加速 先啟動Docker&#xff01;對文件內容有疑問&#xff0c;就去問AI 一、用Docker拉取pytorch鏡像&#xff0c;啟動容器&#xff0c;測試GPU docker pull pytorch/pytorch:2.5.0-cuda12.4…

Linux驅動開發實戰(一):LED控制驅動詳解

Linux驅動開發野火實戰&#xff08;一&#xff09;&#xff1a;LED控制驅動詳解 文章目錄 Linux驅動開發野火實戰&#xff08;一&#xff09;&#xff1a;LED控制驅動詳解引言一、基礎知識1.1 什么是字符設備驅動1.2 重要的數據結構read 函數write 函數open 函數release 函數 二…

Linux上用C++和GCC開發程序實現不同MySQL實例下單個Schema之間的穩定高效的數據遷移

設計一個在Linux上運行的GCC C程序&#xff0c;同時連接兩個不同的MySQL實例&#xff0c;兩個實例中分別有兩個Schema的表結構完全相同&#xff0c;復制一個實例中一個Schema里的所有表的數據到另一個實例中一個Schema里&#xff0c;使用以下快速高效的方法&#xff0c;加入異常…

Redis除了做緩存還能做什么?

Redis 除了作為高性能緩存外&#xff0c;還因其豐富的數據結構和功能&#xff0c;廣泛應用于多種場景。以下是 Redis 的十大核心用途及具體示例&#xff1a; 1. 分布式會話存儲 用途&#xff1a;存儲用戶會話信息&#xff08;如登錄狀態&#xff09;&#xff0c;實現多服務間共…

JBoltAI_SpringBoot如何區分DeepSeek R1深度思考和具體回答的內容(基于Ollama)?

當我們用Ollama運行DeepSeek R1模型&#xff0c;向它提問時&#xff0c;會發現它的回答里是有think標簽的 如果我們直接將Ollama的回復用于生產環境&#xff0c;肯定是不行的&#xff0c;對于不同的場景&#xff0c;前面輸出的一堆內容&#xff0c;可能并不需要在客戶端展示&a…

MySQL 使用 `WHERE` 子句時 `COUNT(*)`、`COUNT(1)` 和 `COUNT(column)` 的區別解析

文章目錄 1. COUNT() 函數的基本作用2. COUNT(*)、COUNT(1) 和 COUNT(column) 的詳細對比2.1 COUNT(*) —— 統計所有符合條件的行2.2 COUNT(1) —— 統計所有符合條件的行2.3 COUNT(column) —— 統計某一列非 NULL 的記錄數 3. 性能對比3.1 EXPLAIN 分析 4. 哪種方式更好&…

將DeepSeek接入vscode的N種方法

接入deepseek方法一:cline 步驟1:安裝 Visual Studio Code 后,左側導航欄上點擊擴展。 步驟2:搜索 cline,找到插件后點擊安裝。 步驟3:在大模型下拉菜單中找到deep seek,然后下面的輸入框輸入你在deepseek申請的api key,就可以用了 讓deepseek給我寫了一首關于天氣的…

AndroidManifest.xml文件的作用

AndroidManifest.xml文件在Android應用程序中扮演著至關重要的角色。它是應用程序的全局配置文件&#xff0c;提供了關于應用程序的所有必要信息&#xff0c;這些信息對于Android系統來說是至關重要的&#xff0c;因為它決定了應用程序的運行方式和權限要求&#xff0c;確保了應…

Mac本地部署Deep Seek R1

Mac本地部署Deep Seek R1 1.安裝本地部署大型語言模型的工具 ollama 官網&#xff1a;https://ollama.com/ 2.下載Deepseek R1模型 網址&#xff1a;https://ollama.com/library/deepseek-r1 根據電腦配置&#xff0c;選擇模型。 我的電腦&#xff1a;Mac M3 24G內存。 這…

React進階之前端業務Hooks庫(五)

前端業務Hooks庫 Hooks原理useStateuseEffect上述問題useState,useEffect 復用的能力練習:怎樣實現一套React過程中的hooks狀態 & 副作用Hooks原理 不能在循環中、條件判斷、子函數中調用,只能在函數最外層去調用useEffect 中,deps 為空,執行一次useState 使用: imp…

從像素到光線:現代Shader開發的范式演進與性能優化實踐

引言 在實時圖形渲染領域&#xff0c;Shader作為GPU程序的核心載體&#xff0c;其開發范式已從早期的固定功能管線演進為高度可編程的計算單元。本文通過解析關鍵技術案例&#xff0c;結合現代圖形API&#xff08;如Vulkan、Metal&#xff09;的特性&#xff0c;深入探討Shade…

(七)消息隊列-Kafka 序列化avro(傳遞)

&#xff08;七&#xff09;消息隊列-Kafka 序列化avro&#xff08;傳遞&#xff09; 客從遠方來&#xff0c;遺我雙鯉魚。呼兒烹鯉魚&#xff0c;中有尺素書。 ——佚名《飲馬長城窟行》 本文已同步CSDN、掘金平臺、知乎等多個平臺&#xff0c;圖片依然保持最初發布的水印&…

PXE批量網絡裝機與Kickstart自動化安裝工具

目錄 一、系統裝機的原理 1.1、系統裝機方式 1.2、系統安裝過程 二、PXE批量網絡裝機 2.1、PXE實現原理 2.2、搭建PXE實際案例 2.2.1、安裝必要軟件 2.2.2、搭建DHCP服務器 2.2.3、搭建TFTP服務器 2.2.4、掛載鏡像并拷貝引導文件到tftp服務啟動引導文件夾下 2.2.5、編…

【全棧開發】從0開始搭建一個圖書管理系統【一】框架搭建

【全棧開發】從0開始搭建一個圖書管理系統【一】框架搭建 前言 現在流行降本增笑&#xff0c;也就是不但每個人都要有事干不能閑著&#xff0c;更重要的是每個人都要通過報功的方式做到平日的各項工作異常飽和&#xff0c;實現1.5人的支出干2人的活計。單純的數據庫開發【膚淺…

部署Flink1.20.1

1、設置環境變量 export JAVA_HOME/cluster/jdk export CLASSPATH.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jarp #export HIVE_HOME/cluster/hive export MYSQL_HOME/cluster/mysql export HADOOP_HOME/cluster/hadoop3 export HADOOP_CONF_DIR$HADOOP_HOME/etc/hadoop …

【超詳細】神經網絡的可視化解釋

《------往期經典推薦------》 一、AI應用軟件開發實戰專欄【鏈接】 項目名稱項目名稱1.【人臉識別與管理系統開發】2.【車牌識別與自動收費管理系統開發】3.【手勢識別系統開發】4.【人臉面部活體檢測系統開發】5.【圖片風格快速遷移軟件開發】6.【人臉表表情識別系統】7.【…

深入了解 Python 中的 MRO(方法解析順序)

文章目錄 深入了解 Python 中的 MRO&#xff08;方法解析順序&#xff09;什么是 MRO&#xff1f;如何計算 MRO&#xff1f;C3 算法的合并規則C3 算法的合并步驟示例&#xff1a;合并過程解析 MRO 解析失敗的場景使用 mro() 方法查看 MRO示例 1&#xff1a;基本用法 菱形繼承與…

數字化賦能:制造業如何突破低效生產的瓶頸?

隨著全球經濟的快速發展與市場需求的變化&#xff0c;制造業面臨著前所未有的壓力與挑戰。生產效率、資源管理、品質控制、成本控制等方面的問題日益突出&#xff0c;尤其是低效生產成為了許多制造企業亟待解決的瓶頸。在這種背景下&#xff0c;數字化轉型成為提升制造業效率的…