ShardingSphere-JDBC SPI扩展点-SQLRewriteContextDecorator

news/2024/5/2 6:04:37/文章来源:https://blog.csdn.net/smile_from_2015/article/details/129233432

装饰器扩展点

  • org.apache.shardingsphere.underlying.route.decorator.RouteDecorator
  • org.apache.shardingsphere.underlying.rewrite.context.SQLRewriteContextDecorator
  • org.apache.shardingsphere.underlying.merge.engine.decorator.ResultDecoratorEngine
  • org.apache.shardingsphere.underlying.merge.engine.merger.ResultMergerEngine

SQL重写装饰器SQLRewriteContextDecorator主要作用是在真正的改写发生前,根据执行SQL的DML类型做一些预处理:

  1. 如果SQL语句是查询语句且分页查询参数不为空,构建分页参数下标对应的具体参数值映射;
  2. 如果SQL语句是插入语句且使用ShardingSphere的唯一键生成策略,构建每条插入语句对应的唯一键值映射;
  3. 构建SQL重写所需的SQLToken生成器。

通过以上三步,可以将SQL重写所需要的Token准备好。因此,如果需要对SQL做一些特殊处理,如SQL改写可以通过在该装饰器中增加自定义SQLToken生成器来实现。

SQLToken 生成器

每个SQL执行都会创建一个ShardingTokenGenerateBuilder来获取SQLTokenGenerator

public void decorate(final ShardingRule shardingRule, final ConfigurationProperties properties, final SQLRewriteContext sqlRewriteContext) {/**省略1,2步*/// 关键代码,获取Token生成器sqlRewriteContext.addSQLTokenGenerators(new ShardingTokenGenerateBuilder(shardingRule, routeContext).getSQLTokenGenerators());
}

ShardingSphere默认内置了多个Token生成器,根据不同的DML语句类型执行不同的SQLTokenGenerator

/**内置Token生成器*/
private Collection<SQLTokenGenerator> buildSQLTokenGenerators() {Collection<SQLTokenGenerator> result = new LinkedList<>();addSQLTokenGenerator(result, new TableTokenGenerator());addSQLTokenGenerator(result, new DistinctProjectionPrefixTokenGenerator());addSQLTokenGenerator(result, new ProjectionsTokenGenerator());addSQLTokenGenerator(result, new OrderByTokenGenerator());addSQLTokenGenerator(result, new AggregationDistinctTokenGenerator());addSQLTokenGenerator(result, new IndexTokenGenerator());addSQLTokenGenerator(result, new OffsetTokenGenerator());addSQLTokenGenerator(result, new RowCountTokenGenerator());addSQLTokenGenerator(result, new GeneratedKeyInsertColumnTokenGenerator());addSQLTokenGenerator(result, new GeneratedKeyForUseDefaultInsertColumnsTokenGenerator());addSQLTokenGenerator(result, new GeneratedKeyAssignmentTokenGenerator());addSQLTokenGenerator(result, new ShardingInsertValuesTokenGenerator());addSQLTokenGenerator(result, new GeneratedKeyInsertValuesTokenGenerator());return result;
}

应用

在业务应用中,有些场景的数据不允许物理删除,只能通过逻辑删除,即在数据表中添加与业务无关的字段来标记该条数据是否已删除。为了避免每次写SQL语句都要带上业务无关的标记字段,可以通过配置+SQL改写的方式,自动在已配置表的执行SQL语句中加入标记字段。

SQL 改写

通过源码分析,SQL语句改写主要依赖SQLTokenGenerator,我们可以通过自定义自己的SQLToken生成器来达到SQL改写的目的。

自定义SQLToken生成器

通过ShardingSphere自带的Token接口OptionalSQLTokenGenerator实现自定义Token生成器,由于在插入语句和查询语句中插入字段的方式是不一样的(插入语句字段之前用逗号分隔,查询语句where条件后用and分隔),
所以即使插入一个字段也需要实现多个Token生成器,下面以简单的查询语句类型为例

@Override
public boolean isGenerateSQLToken(SQLStatementContext sqlStatementContext) {// 检查是否需要插入标记字段,校验是否是查询语句,不是查询则跳过走下个Token生成器// 省略......// 获取逻辑表名,表名不存在不继续执行// 省略......// 判断白名单,可通过白名单配置哪些表不需要加该标记字段// 省略......// 获取自定义的标记字段信息TagField info = SQLTagUtil.getTagField(logicTableName);// 校验 where 条件是否已存在该标记字段,存在则不继续添加SelectStatement statement = (SelectStatement)sqlStatementContext.getSqlStatement();return SQLTagUtil.checkAddTagField(statement.getWhere().get(), info);
}@Override
public SQLToken generateSQLToken(SelectStatementContext sqlStatementContext) {// 获标记字段配置信息TagField info = SQLTagUtil.getTagField(logicTableName);// 获取 where 条件,在最后追加自定义字段Optional<WhereSegment> sqlSegment = sqlStatementContext.getSqlStatement().getWhere();Preconditions.checkState(sqlSegment.isPresent());// 根据之前SQL解析结果,计算插入下标索引,生成自定义Tokenreturn new CustomQueryColumnToken(sqlSegment.get().getStopIndex() + 1, SQLTagUtil.getSql(info));
}
自定义SQLToken

在上面自定义的SQLToken生成器中,我们自定义了CustomQueryColumnToken,该Token是可以拼接在where条件之后的

public final class CustomQueryColumnToken extends SQLToken implements Attachable {private final String column;public CustomQueryColumnToken(final int startIndex, final String column) {super(startIndex);this.column = column;}@Overridepublic String toString() {// 关键位置,主要拼接 and 语句return String.format(" and %s", column);}
}
追加SQLToken生成器

在自定义完Token生成器后,我们可以将该生成器追加到buildSQLTokenGenerators的后面,形如

/**内置Token生成器*/
private Collection<SQLTokenGenerator> buildSQLTokenGenerators() {Collection<SQLTokenGenerator> result = new LinkedList<>();// 省略部分生成器addSQLTokenGenerator(result, new ShardingInsertValuesTokenGenerator());addSQLTokenGenerator(result, new GeneratedKeyInsertValuesTokenGenerator());// 追加自定义的SQLToken生成器addSQLTokenGenerator(result, new TagFieldQueryColumnTokenGenerator());addSQLTokenGenerator(result, new TagFieldUpdateColumnTokenGenerator());addSQLTokenGenerator(result, new TagFieldInsertColumnTokenGenerator());addSQLTokenGenerator(result, new TagFieldInsertValuesTokenGenerator());return result;
}

到这里,我们已经将要插入的字段包装为一个自定义Token,接下来来,SQL改写引擎会去遍历SQLToken生成器获取具体的字段信息,并完成最终的SQL改写流程。

改进

本例中只添加一个字段,就需要在buildSQLTokenGenerators方法内追加4个自定义SQLToken生成器。如果是在SQL改写时,添加两个字段甚至更多的字段,需要不停的改动buildSQLTokenGenerators方法,违反了开闭原则。为了提高扩展性,我们定义了一个MySQLTokenGenerateHook来增加扩展SQLToken生成器

public interface MySQLTokenGenerateHook {/*** 自定义扩展生成器** @return 扩展 TOKEN 生成器*/LinkedList<SQLTokenGenerator> extendGenerators();
}

将buildSQLTokenGenerators方法修改为以下形式

private final Collection<MySQLTokenGenerateHook> tokenGenerateHooks = NewInstanceServiceLoader.newServiceInstances(MySQLTokenGenerateHook.class);
static {NewInstanceServiceLoader.register(MySQLTokenGenerateHook.class);
}private Collection<SQLTokenGenerator> buildSQLTokenGenerators() {Collection<SQLTokenGenerator> result = new LinkedList<>();// 省略默认生成器// 添加扩展生成器tokenGenerateHooks.forEach(hook -> hook.extendGenerators().forEach(ext -> addSQLTokenGenerator(result, ext)));return result;
}

每个扩展点实现里只需要将各自字段的SQLToken生成器创建并返回即可。

注意:每一个SQL执行上下文中都需要创建新的SQLToken生成器,多线程共用SQLToken生成器会导致线程安全问题,引起SQL改写语句错乱。

ResultDecoratorEngine与ResultMergerEngine主要是对查询的结构进行装饰合并。如进行分页排序查询,需要将多个表中查出的数据按照指定的排序维度进行合并返回最终的分页数据,由于没有对这两个装饰器进行过多的应用,因此不对这两个装饰器进行展开分析。

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

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

相关文章

ledcode【用队列实现栈】

目录 题目描述&#xff1a; 解析题目 代码解析 1.封装一个队列 1.2封装带两个队列的结构体 1.3封装指向队列的结构体 1.4入栈函数实现 1.5出栈函数实现 1.6取栈顶数据 1.7判空函数实现 题目描述&#xff1a; 解析题目 这个题我是用c语言写的&#xff0c;所以队列的pu…

JavaSE-3 Java运行原理

一、Java的运行过程 &#x1f34e;Java程序运行时,必须经过编译和运行两个步骤。 首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件。然后Java虚拟机将字节码文件进行解释执行,并将结果显示出来。具体过程如下图所示。 &#x1f349;Java程序的运行过…

【Python数据挖掘入门】2.2文本分析-中文分词(jieba库cut方法/自定义词典load_userdict/语料库分词)

中文分词就是将一个汉字序列切分成一个一个单独的词。例如&#xff1a; 另外还有停用词的概念&#xff0c;停用词是指在数据处理时&#xff0c;需要过滤掉的某些字或词。 一、jieba库 安装过程见&#xff1a;https://blog.csdn.net/momomuabc/article/details/128198306 ji…

数字IC手撕代码--小米科技(除法器设计)

前言&#xff1a; 本专栏旨在记录高频笔面试手撕代码题&#xff0c;以备数字前端秋招&#xff0c;本专栏所有文章提供原理分析、代码及波形&#xff0c;所有代码均经过本人验证。目录如下&#xff1a;1.数字IC手撕代码-分频器&#xff08;任意偶数分频&#xff09;2.数字IC手撕…

原始GAN-pytorch-生成MNIST数据集(代码)

文章目录原始GAN生成MNIST数据集1. Data loading and preparing2. Dataset and Model parameter3. Result save path4. Model define6. Training7. predict原始GAN生成MNIST数据集 原理很简单&#xff0c;可以参考原理部分原始GAN-pytorch-生成MNIST数据集&#xff08;原理&am…

记一次线上es慢查询导致的服务不可用

现象 某日线上业务同学反馈订单列表查询页面一直loding&#xff0c;然后提示请求超时&#xff0c;几分钟之后恢复正常 接到报障之后&#xff0c;马上根据接口URL&#xff0c;定位到了请求链路&#xff0c;发现是es查询超时&#xff0c;这里我们的业务订单表数据是由几百万的&a…

如何基于MLServer构建Python机器学习服务

文章目录前言一、数据集二、训练 Scikit-learn 模型三、基于MLSever构建Scikit-learn服务四、测试模型五、训练 XGBoost 模型六、服务多个模型七、测试多个模型的准确性总结参考前言 在过去我们训练模型&#xff0c;往往通过编写flask代码或者容器化我们的模型并在docker中运行…

Python学习笔记202302

1、numpy.empty 作用&#xff1a;根据给定的维度和数值类型返回一个新的数组&#xff0c;其元素不进行初始化。 用法&#xff1a;numpy.empty(shape, dtypefloat, order‘C’) 2、logging.debug 作用&#xff1a;Python 的日志记录工具&#xff0c;这个模块为应用与库实现了灵…

C# Sqlite数据库加密

sqlite官方的数据库加密是收费的&#xff0c;而且比较贵。 幸亏微软提供了一种免费的方法。 1 sqlite加密demo 这里我做了一个小的demo演示如下&#xff1a; 在界面中拖入数据库名、密码、以及保存的路径 比如我选择保存路径桌面的sqlite目录&#xff0c;数据库名guigutool…

Verilog 学习第五节(串口接收部分)

小梅哥串口部分学习part2 串口通信接收原理串口通信接收程序设计与调试巧用位操作优化串口接收逻辑设计串口接收模块的项目应用案例串口通信接收原理 在采样的时候没有必要一直判断一个clk内全部都是高/低电平&#xff0c;如果采用直接对中间点进行判断的话&#xff0c;很有可能…

Linux 红帽9.0 本地源 与 网络源 搭建

本次我们使用的是 redhat 9.0 版本&#xff0c;是redhat 的最新版本&#xff0c;我们一起来对其进行 本地仓库 和 网络仓库的搭建部署~&#xff01;&#xff01;关于 本地仓库&#xff08; 本地源 &#xff09;&#xff0c;和 网络仓库 &#xff08; 网络源 &#xff09;&#…

ESP32蓝牙配网

注意********menuconfig 配置&#xff08;必须打开蓝牙我这是C2所以使用NimBLE &#xff09;可以直接从demo的配置文件拷贝 Component config ---> Bluetooth ---> NimBLE - BLE only Component config ---> Bluetooth ---> NimBLE Options ---> Enable blufi…

计算结构体大小

计算结构体大小 目录计算结构体大小一. 结构体内存对齐1. 简介2. 嵌套结构体二. offsetof三. 内存对齐的意义四. 修改默认对齐数一. 结构体内存对齐 以字节&#xff08;bety&#xff09;为单位 1. 简介 对于结构体成员在内存里的存储&#xff0c;存在结构体的对齐规则&#…

Vue下载安装步骤的详细教程(亲测有效) 1

目录 一、【准备工作】nodejs下载安装(npm环境) 1 下载安装nodejs 2 查看环境变量是否添加成功 3、验证是否安装成功 4、修改模块下载位置 &#xff08;1&#xff09;查看npm默认存放位置 &#xff08;2&#xff09;在 nodejs 安装目录下&#xff0c;创建 “node_global…

Java查漏补缺(14)数据结构剖析、一维数组、链表、栈、队列、树与二叉树、List接口分析、Map接口分析、Set接口分析、HashMap的相关问题

Java查漏补缺&#xff08;14&#xff09;数据结构剖析、一维数组、链表、栈、队列、树与二叉树、List接口分析、Map接口分析、Set接口分析、HashMap的相关问题本章专题与脉络1. 数据结构剖析1.1 研究对象一&#xff1a;数据间逻辑关系1.2 研究对象二&#xff1a;数据的存储结构…

Laravel框架04:视图与CSRF攻击

Laravel框架04&#xff1a;视图与CSRF攻击一、视图概述二、变量分配与展示三、模板中直接使用函数四、循环与分支语法标签五、模板继承、包含1. 继承2. 包含六、外部静态文件引入七、CSRF攻击概述八、从CSRF验证中排除例外路由一、视图概述 视图存放在 resources/views 目录下…

MyBatis学习笔记(七) —— 特殊SQL的执行

7、特殊SQL的执行 7.1、模糊查询 模糊查询的三种方式&#xff1a; 方式1&#xff1a;select * from t_user where username like ‘%${mohu}%’ 方式2&#xff1a;select * from t_user where username like concat(‘%’,#{mohu},‘%’) 方式3&#xff1a;select * from t_u…

收集分享一些AI工具第三期(网站篇)

感谢大家对于内容的喜欢&#xff0c;目前已经来到了AI工具分享的最后一期了&#xff0c;目前为止大部分好用的AI工具都已经介绍给大家了&#xff0c;希望大家可以喜欢。 image-to-sound-fx (https://huggingface.co/spaces/fffiloni/image-to-sound-fx) 图片转换为相对应的声音…

2.27 junit5常用语法

一.了解junitjunit是一个开源的java单元测试框架,java方向使用最广泛的单元测试框架.所需要的依赖<dependencies><!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --><dependency><groupId>org.seleniumhq.selenium&l…

笔记本触摸板没反应怎么办?处理方法看这些

触摸板在笔记本电脑中是非常重要的一部分&#xff0c;很多用户都会选择使用触摸板代替鼠标。然而&#xff0c;有时你可能会发现&#xff0c;你的笔记本电脑触摸板没反应&#xff0c;无法正常使用。这对于日常使用来说是非常困扰的&#xff0c;但不用担心&#xff0c;我们将在这…