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.mzph.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;数字化转型成为提升制造业效率的…