Mybaits拦截器实现水平分表

前面我们已经学习了解过了数据库常用的分库分表方案,本节以水平分表为例来实战下.

需求背景

最近项目中的几张表数据行超过了1000万行,所以需要对这些表进行水平分表,提高数据查询的性能。可选的方案有sharding-jdbc中间件还有就是Mybatis拦截器。由于使用的是Pg数据库,并且Pg数据库支持很多函数,以及复杂的sql查询语句,使用sharding-jdbc可能会有意想不到的坑,所以决定采用Mybatis拦截器的方式。

分表思路

这里我需要分表的表名为process_log,这里不是根据正常的id字段去分表(因为这张表连id字段都没有…),而是选择这张表的唯一字段form_data_code来作为分表字段,将form_data_code字段值进行hashCode()然后进行取模。目前这张表的数据足足5000多万,考虑之后还会增加,需要将表数据控制在百万级内,所以决定分表20张。

// 1.准备20张表
结构同表process_log的20张表,process_log_0 ->process_log_19
// 2.分表健formDataCode,使用java hash算法
Math.abs(formDataCode.hashCode() % 20);
// 3.迁移数据

Mybaits拦截器实现

实现自定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SegmentTable {/*** 表名*/String[] tableName();/*** 算法策略*/Class[] strategyClazz();
}
// 需要分表的table的Mapper接口
@SegmentTable(tableName = {"process_log"}, strategyClazz ={ProcessLogStrategy.class})
public interface ProcessLogMapper {// todo}

实现算法策略

// 策略模式
public interface ShardTableStrategy {/*** 分表算法** @param statementHandler* @return*/String shardAlgorithm(StatementHandler statementHandler);
}
public class ShardTableContext {private ShardTableStrategy tableStrategy;public ShardTableContext(ShardTableStrategy tableStrategy) {this.tableStrategy = tableStrategy;}public String doShardAlgorithm(StatementHandler statementHandler){return tableStrategy.shardAlgorithm(statementHandler);}
}

process_log策略实现

/*** process_log 分表算法**/
public class ProcessLogStrategy implements ShardTableStrategy {private static final Logger logger = LoggerFactory.getLogger(ProcessLogStrategy.class);/*** 原始表名*/private final static String PROCESS_LOG_ORIGIN_TABLE_NAME = "process_log";private final static String TABLE_LINE = "_";/*** 分表20张*/public final static Integer PROCESS_LOG_TABLE_NUM = 20;/*** 特殊处理字段*/private final static String PROCESS_LOG_TABLE_CONFIRM_INDEX = "subTableConfirmIndex";/*** 分表字段*/private final static String PROCESS_LOG_TABLE_SUB_FIELD = "formDataCode";@Overridepublic String shardAlgorithm(StatementHandler statementHandler) throws RuntimeException {AppEnv appEnv = ApplicationContextUtil.getApplicationContext().getBean(AppEnv.class);if(!appEnv.isShardingTableProcessLog()){return PROCESS_LOG_ORIGIN_TABLE_NAME;}BoundSql boundSql = statementHandler.getBoundSql();Object parameterObject = boundSql.getParameterObject();// 参数值Map param2ValeMap = JSONObject.parseObject(JSON.toJSONString(parameterObject), Map.class);logger.info("ProcessLogStrategy test!!! param2ValeMap={}", JSON.toJSONString(param2ValeMap));// 特殊处理foreach循环语句Object confirmIndexValue = param2ValeMap.get(PROCESS_LOG_TABLE_CONFIRM_INDEX);if (confirmIndexValue != null) {logger.info("handle success, sql exist param confirmIndexValue={}", confirmIndexValue);return PROCESS_LOG_ORIGIN_TABLE_NAME + TABLE_LINE + confirmIndexValue;}Object subFieldValue = param2ValeMap.get(PROCESS_LOG_TABLE_SUB_FIELD);if (MapUtils.isEmpty(param2ValeMap) || subFieldValue == null) {throw new MybatisInterceptorException("process_log is subTable so must have subFiledValue!");}return PROCESS_LOG_ORIGIN_TABLE_NAME + TABLE_LINE + Math.abs(subFieldValue.hashCode() % PROCESS_LOG_TABLE_NUM);}}

拦截器intercept()执行逻辑

mybatis拦截器必须实现Interceptor接口

public interface Interceptor {  // 拦截器执行的逻辑方法Object intercept(Invocation invocation) throws Throwable;  // 用来封装目标对象。可以返回目标对象本身也可以根据实际需要,创建一个代理对象Object plugin(Object target);// 在Mybatis进行配置插件的时候可以配置自定义相关属性void setProperties(Properties properties);
}
 @Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler statementHandler = (StatementHandler) invocation.getTarget();// 全局操作读对象MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());// @SegmentTable -- 只拦截有注解的MapperSegmentTable segmentTable = getSegmentTable(metaObject);if (segmentTable == null) {return invocation.proceed();}// 1.对value进行算法 -> 确定表名Class strategyClazz = segmentTable.strategyClazz();ShardTableStrategy strategy = (ShardTableStrategy) strategyClazz.newInstance();String index = new ShardTableContext(strategy).doShardAlgorithm(statementHandler);logger.info("ShardTableInterceptor segmentTable={},index={}", JSON.toJSONString(segmentTable), index);// 2.替换表名// 获取原始sqlString tableName = segmentTable.tableName();String sql = (String) metaObject.getValue(BOUND_SQL_NAME);metaObject.setValue(BOUND_SQL_NAME, sql.replaceFirst(tableName, tableName + index));return invocation.proceed();}

总结遇到的问题

1.Mapper文件问题
老系统比较混乱,存在多表关联查询,有些Mapper.xml对象对应的sql不是唯一表。这里需要注意,因为注解是针对整个Mapper文件的,只要是上面的sql都会拦截。但是需要分表的sql都必须要有分表字段。需要避免不分表的sql走策略算法。 最好将需要分表的表拆成单表,逻辑在代码里处理。

2.分页插件pagehelper导致自定义插件无效
在sqlSessionFactory对象中放入拦截器对象,如果系统中有使用Mybatis对分页插件,要注意与自定义拦截器对顺序,拦截器底层采用责任链对方式,通常都会返回invocation.proceed()传递,但是分页插件没有返回。所以需要调整注册顺序.

3.针对sql中对foreach循环
sql查询中用到了分表key的集合,这种情况,在应用层提前使用hash算法,找到所在的表。

4.迁移数据
数据库的hash算法和Java的hash算法是不一致的,所以要确定两边的算法一直,大多数数据库是提供自定义算法的,本次是Pg数据.

// pg自定义java的hashCode算法
DROP FUNCTION IF EXISTS  hash_code(text);
CREATE FUNCTION hash_code(text) RETURNS integerLANGUAGE plpgsql
AS
$$
DECLAREi integer := 0;DECLAREh bigint  := 0;
BEGINFOR i IN 1..length($1)LOOPh = (h * 31 + ascii(substring($1, i, 1))) & 4294967295;END LOOP;RETURN cast(cast(h AS bit(32)) AS int4);
END;
$$;

总结

以上便是水平分表的一次实践,就是提供一种分表的思路吧,加深下分库分表的理解.

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

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

相关文章

Redis对象——内存回收,对象共享和空转时长

一. 内存回收 因为C语言不具备内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数技术实现内存回收机制。通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。 内每一个对象的引用计数信息…

uniapp自定义顶部导航栏

<!-- 自定义导航栏 --><view style"border-bottom: 1px solid #f4f4f4;"><u-navbar :border"true" placeholder"true" leftText"返回" title"调动申请" :safeAreaInsetTop"true"><view styl…

平台工程与 DevOps 和 SRE 有何不同?

在现代软件开发和运营的动态领域中 &#xff0c;平台工程、DevOps 和站点可靠性工程 (SRE) 等术语 经常使用&#xff0c;有时可以互换使用&#xff0c;这常常会导致进入或浏览这些领域的专业人员感到困惑。了解这些概念之间的细微差别对于努力构建强大且可扩展的系统的组织至关…

国产Apple Find My「查找」认证芯片-伦茨科技ST17H6x芯片

深圳市伦茨科技有限公司&#xff08;以下简称“伦茨科技”&#xff09;发布ST17H6x Soc平台。成为继Nordic之后全球第二家取得Apple Find My「查找」认证的芯片厂家&#xff0c;该平台提供可通过Apple Find My认证的Apple查找&#xff08;Find My&#xff09;功能集成解决方案。…

连连看游戏

连通块记忆性递归的综合运用 这里x&#xff0c;y的设置反我平常的习惯&#xff0c;搞得我有点晕 实际上可以一输入就交换x&#xff0c;y的数据的 如果设置y1为全局变量的话会warning&#xff1a; warning: built-in function y1 declared as non-function 所以我改成p和q了…

一些好用的VSCode扩展

可以在扩展这里直接搜索需要的扩展&#xff0c;点击安装即可。 1.Chinese 中文扩展&#xff0c;就是说虽然咱们懂点英语&#xff0c;但还是中文看着方便 2.Auto Rename Tag 当你重命名一个HTML 标签时&#xff0c;会自动重命名与他配对的HTML 标签 当你选择h4这个标签时&…

系列三、DDL

一、DDL 1.1、概述 DDL是英文单词Data Definition Language的缩写&#xff0c;中文意思为数据定义语言&#xff0c;是用来定义数据库对象(数据库&#xff0c;表&#xff0c;字段)的。 1.2、数据库操作 1.2.1、查询所有数据库 show databases; 1.2.2、创建数据库 # 语法 cre…

JAVA基础知识:注解

Java注解是一种元数据&#xff08;metadata&#xff09;机制&#xff0c;它允许我们在代码中添加额外的信息和标记。注解在Java开发中起着重要的作用&#xff0c;可以用于编写文档、代码分析、编译时检查和运行时处理等方面。本文将详细介绍Java注解的基础知识&#xff0c;包括…

云原生基础入门概念

文章目录 云原生的概念云原生的关键技术为何选择云原生&#xff1f;云原生的实际应用 当谈及现代软件开发和IT基础架构时&#xff0c;云原生成为了一个备受关注的话题。它代表了一种软件架构和开发方法&#xff0c;旨在充分利用云计算环境的优势&#xff0c;以提高应用程序的可…

【AI美图】第02期效果图,AI人工智能全自动绘画,美图欣赏

今天给大家献上一组最新提示词 参照图生成图像 依据参照图生成新的图像需要掌握一些技巧&#xff0c;以下是一些可能有用的技巧&#xff1a; 观察参照图&#xff1a;在开始生成新图像之前&#xff0c;仔细观察参照图是非常重要的。你需要了解图像的布局、颜色、线条、细节等…

新一代“垫图”神器,IP-Adapter的完整应用解读

导读 不用训练lora&#xff0c;一张图就能实现风格迁移&#xff0c;还支持多图多特征提取&#xff0c;同时强大的拓展能力还可接入动态prompt矩阵、controlnet等等&#xff0c;这就是IP-Adapter&#xff0c;一种全新的“垫图”方式&#xff0c;让你的AIGC之旅更加高效轻松。 …

2024年 vue3 使用wow.js

准备用vue3搞了懒加载动画功能 被反复折磨了好久&#xff0c;国内资料太少&#xff0c;官方又没找到demo 原来是vue3不支持wowjs vue3换成了wow.js&#xff0c;太容易混淆了&#xff01; npm 安装wow.js main.js 引入import wow.js/css/libs/animate.css 核心代码 import…

智慧工地源码(微服务+Java+Springcloud+Vue+MySQL)

智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台&#xff0c;是一种全新的管理模式&#xff0c;能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度&#xff0c;以及施工过程管理的进度、质量、安全三…

Python Socket编程

Python Socket编程 文章目录 Python Socket编程1. 弄懂HTTP、Socket、TCP这几个概念五层网络模型 2. client和server实现通信Socket编程模式指南代码实现 3. socket实现聊天和多用户连接4. socket模拟http请求5. socket使用I/O多路复用模式模拟http请求 1. 弄懂HTTP、Socket、T…

k8s-1.24.0版本部署

基础配置[三台centos] 1.关闭防火墙与selinux systemctl stop firewalld systemctl disable firewalld sed -i ‘s/enforcing/disabled/’ /etc/selinux/config setenforce 0 2.添加host记录 cat >>/etc/hosts <<EOF 192.168.180.190 k8s-master 192.168.180.180 k…

51单片机的外部中断的以及相关寄存器的讲解

中断系统 本文主要涉及8051单片机的中断系统的讲解与使用 其中包括中断相关寄存器的介绍与使用以及外部中断初始化的代码分析。 文章目录 中断系统一、 中断的介绍二、 中断结构及相关寄存器2.1 中断源 2.2 中断请求控制器2.2.1 TCON寄存器2.2.2 SCON寄存器2.2.3 中断允许寄存器…

【每日一题】【12.15】2415.反转二叉树的奇数层

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 2415. 反转二叉树的奇数层https://leetcode.cn/problems/reverse-odd-levels-of-binary-tree/ 今天终于碰到了一个mid题目&#x…

数据库常用分库分表方案

为什么需要分库分表 分库分表是因应数据库处理大规模数据时所面临的挑战而出现的解决方案. // 提高性能 单个数据库在数据量增加时容易出现性能瓶颈。分库分表可以减轻单个数据库的负担&#xff0c;提高系统的读写性能和响应速度. // 提高并发能力 大量用户同时访问数据库可能…

N-Channel Trench Power MOSFET FMA30H150SL

FMA30H150SL N-Channel Trench Power MOSFET FMA30H150SL Application &#xff1a;  LCD TV  Notebook  Elevator  Inductive heating  Power tools  Broadband FMA30H150SL Features &#xff1a;  30V,150A  RDS(ON)2.4mΩ (Typ.) VGS 10V …

若依 ruoyi-vue3 集成aj-captcha实现滑块、文字点选验证码

目录 0. 前言0.1 说明 1. 后端部分1.1 添加依赖1.2. 修改 application.yml1.3. 新增 CaptchaRedisService 类1.4. 添加必须文件1.5. 移除不需要的类1.6. 修改登录方法1.7. 新增验证码开关获取接口1.8. 允许匿名访问 2. 前端部分&#xff08;Vue3&#xff09;2.1. 新增依赖 cryp…