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")
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"
}
请求头

运行结果

相关文章:
spring结合mybatis多租户实现单库分表
实现单库分表 思路:student表数据量大,所以将其进行分表处理。一共有三个分表,分别是student0,student1,student2,在新增数据的时候,根据请求头中的meta-tenant参数决定数据存在哪张表表。 数…...
面向对象编程(OOP)基础:Java入门指南
引言 随着计算机技术的发展,软件的应用越来越复杂,单个程序的功能也逐渐增多。为了提高代码的复用性和可维护性,Java语言引入了**面向对象编程(Object-Oriented Programming, OOP)**这一设计理念。 OOP是一种设计程序…...
day7作业
编写一个如下场景: 有一个英雄Hero类,私有成员,攻击(Atx),防御(Defense),速度(Speed),生命值(Blood),以及所有的set get 方…...
图像处理之图像边缘检测算法
目录 1 图像边缘检测算法简介 2 Sobel边缘检测 3 经典的Canny边缘检测算法 4 演示Demo 4.1 开发环境 4.2 功能介绍 4.3 下载地址 参考 1 图像边缘检测算法简介 图像边缘检测是计算机视觉和图像处理中的基本问题,主要目的是提取图像中明暗变化明显的边缘细节…...
第二十五 :搭建 pinia 环境
第一步:npm install pinia 第二步:操作src/main.ts import { createApp } from vue import App from ./App.vue /* 引入createPinia,用于创建pinia */ import { createPinia } from pinia /* 创建pinia */ const pinia createPinia(…...
学习Java数组操作:从基础到高级技巧详解
在Java编程中,数组是一种非常基础且常用的非 primitives 数据结构,它用于存储一组相同类型的值。无论是数据处理、遍历还是其他操作,数组都是一个不可或缺的工具。本文将从数组的基本概念开始,逐步介绍常用的操作方法,…...
算法题(79):两个数组的交集
审题: 本题需要我们查找两个给定数组的无重复数据交集,并以数组的形式返回 思路: 方法一:set 之前我们学习过unordered_set的使用,但是unordered_set是无序的,而这里我们的比对算法需要有序数据,…...
TFChat:腾讯大模型知识引擎+飞书机器人实现AI智能助手
效果 TFChat项目地址 https://github.com/fish2018/TFChat 腾讯大模型知识引擎用的是DeepSeek R1,项目为sanic和redis实现,利用httpx异步处理流式响应,同时使用buffer来避免频繁调用飞书接口更新卡片的网络耗时。为了进一步减少网络IO消耗&…...
Linux红帽:RHCSA认证知识讲解(四)修改远程配置文件,取消root禁用,便于使用root身份远程
Linux红帽:RHCSA认证知识讲解(四)修改远程配置文件,取消root禁用,便于使用root身份远程 前言一、远程连接的用途和原因二、通过 ssh 远程登陆系统三、默认限制及解决方案(一)非常规方法一&#…...
验证码介绍及生成与验证(HTML + JavaScript实现)
验证码介绍及生成与验证(HTML JavaScript实现) 验证码 验证码(全自动区分计算机和人类的图灵测试,CAPTCHA ,Completely Automated Public Turing test to tell Computers and Humans A…...
文心一言AI创意画
介绍 文心一言是百度推出的新一代知识增强大语言模型,属于文心大模型家族的新成员。它能够与人对话互动、回答问题、协助创作,高效便捷地帮助人们获取信息、知识和灵感。 特点 文心一言基于数万亿数据和数千亿知识进行融合学习,采用预训…...
WebRTC解析
一、WebRTC 协议概述 WebRTC(Web Real-Time Communication)是由 Google 发起并成为 W3C 标准的实时音视频通信技术,核心特点: 零插件:浏览器原生支持端到端加密(SRTP DTLS)P2P 优先架构&…...
升级Office软件后,Windows 系统右键里没有新建Word、Excel、PowerPoint文件的解决办法
我办公用的电脑,Office 2013 已经用了好多年,最近突发奇想给升级到了 Ofiice 2024。升级过程还蛮顺利的,但是安装完成后,发现点右键里没有新建Word、Excel、PowerPoint,开始菜单里 Word、Excel、PowerPoint 使用都正常…...
车载DoIP诊断框架 --- 连接 DoIP ECU/车辆的故障排除
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...
洛谷每日1题-------Day4__陶陶摘苹果
# P1046 [NOIP 2005 普及组] 陶陶摘苹果 ## 题目描述 陶陶家的院子里有一棵苹果树,每到秋天树上就会结出 $10$ 个苹果。苹果成熟的时候,陶陶就会跑去摘苹果。陶陶有个 $30$ 厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩…...
萌新学 Python 之模块管理
模块就是一个 python 代码文件,模块可以包含函数、类,可以提高代码复用率提高效率 python 模块主要分为三种: 1.内置模块:python 自带的模块,导入后可以直接使用,比如 import 模块名 2.第三方模块&#…...
6.3 - UART串口数据发送之中断
文章目录 1 实验任务2 系统框图3 软件设计 1 实验任务 本实验使用中断方式实现UART串口数据的连续发送。 2 系统框图 参见6.1。 3 软件设计 注意事项: 系统上电、程序下载后,此时TX FIFO虽然为空,但并不会触发空中断;空中断…...
Fisher信息矩阵(Fisher Information Matrix, FIM)与自然梯度下降:机器学习中的优化利器
Fisher信息矩阵与自然梯度下降:机器学习中的优化利器 在机器学习尤其是深度学习中,优化模型参数是一个核心任务。我们通常依赖梯度下降(Gradient Descent)来调整参数,但普通的梯度下降有时会显得“笨拙”,…...
Mysql基础-多表查询(详细版)
目录 一、表的关系类型与适用场景二、连接方式与使用场景三、易错点与注意事项四、总结 一、表的关系类型与适用场景 1. 一对一关系 场景:一个表的记录对应另一个表的唯一记录 案例:用户表 用户详情表 CREATE TABLE users (id INT PRIMARY KEY,name…...
港科大提出开放全曲音乐生成基础模型YuE:可将歌词转换成完整歌曲
YuE是港科大提出的一个开源的音乐生成基础模型,专为音乐生成而设计,专门用于将歌词转换成完整的歌曲(lyrics2song)。它可以生成一首完整的歌曲,时长几分钟,包括朗朗上口的声乐曲目和伴奏曲目。YuE 能够模拟…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
