来来来,告诉你一种通过对象构建查询语句的方法
无论使用什么ORM框架,我们最终都是通过SQL语句访问数据库的,只要能够自动构建SQL语句,特别是查询子句,就可以不再需要在代码中直接维护SQL语句。
问题背景
主流ORM框架都支持通过实体对象映射SQL语句中表名和列名,接下来我来介绍一下如何通过另一个对象构建SQL语句中WHERE子句,核心是通过字段构建各类查询条件。
WHERE子句中的查询条件主要包括三类:
- 比较查询条件
- 逻辑查询条件
- 子查询条件
其中,比较查询条件基于数理逻辑中的谓词逻辑(通过大于、小于等比较运算符进行比较运算),逻辑查询条件基于布尔代数(通过AND/OR/NOT进行逻辑运算),子查询条件由于嵌套的是一条完整的SELECT语句还需要基于关系代数。
除了这三类查询条件,ORM框架还需要解决查询条件基于查询参数的赋值进行动态组合的问题,即动态查询问题。这个特性可以基于组合数学中的知识进行解释。
所以,为了支持查询条件的动态构建,ORM框架至少需要满足以下4个特性:
- 构建比较查询条件;
- 构建逻辑查询条件;
- 构建子查询条件;
- 基于查询参数对查询条件进行动态组合。
动态查询问题
对于一个提供n个查询参数的查询接口,用户每次会填写其中的k个查询参数提交一次查询请求,查询接口需要根据这k个查询参数动态得将对应的查询条件拼接为查询子句。
从含有n个元素的集合中选择k个元素构建一个子集,这是组合数学中典型的子集选择问题。
$$ \sum_{k=0}^{n} \binom{n}{k} = 2^n $$ 由公式可知,对于一个提供n个查询参数的查询接口,一共可以构建$2^n$种查询子句。
考虑到软件的维护性和复用性,我们不可能硬编码$2^n$条查询子句,只能通过对查询参数进行判断来确定是否将其对应的查询条件拼接为查询子句。这就是动态查询问题的由来。
传统的解决方案是通过if语句进行构建。每条if语句首先对查询参数进行判断,输出TRUE和FALSE两种判断结果,对应是否执行if块内的查询条件拼接操作,这样正好有$2^n$种拼接结果。
但是这种方案的问题在于需要为每个查询参数编写一段if语句。当查询参数越来越多时,if语句会变得越来越多,从而增加代码的维护难度。
查询对象映射方法
查询对象映射方法是一种根据查询对象的字段赋值将对应的查询条件组合为查询子句的方法。
对于一个定义有n个字段的对象而言,每个字段可以有赋值和未赋值两种状态,n个字段的赋值组合正好有$2^n$种。如果对象中的每个字段都能构建一条查询语句,那么我们就可以利用对象的$2^n$种赋值组合来构建$2^n$种查询子句。
也就是说,基于对象构建查询子句,天然满足特性4。我们将这种用于构建查询子句的对象称为查询对象,将通过查询对象构建查询子句的方法称为查询对象映射方法。
接下来,我们重点关注如何通过字段构建三类查询条件即可。
通过谓词后缀字段映射比较查询条件。比较查询条件通常由列名、比较运算符和参数三部分构成。在DSL(领域特定语言)中,通常会使用谓词短语来表示比较运算符,例如,eq
代表等于=
、gt
代表大于>
等等,condition.gt("age", 30)
就表示查询条件age > 30
。
我们把谓词短语附加在列名后作为字段名称来表示查询条件,例如字段ageGt 就表示查询条件age > ?。类似的后缀还有Eq、Ne、Ge、Lt、Le、In、NotIn、Null、Like等等,这样我们就可以通过字段的后缀来映射不同的比较查询条件。
通过逻辑后缀字段构建逻辑查询条件。逻辑查询条件是由逻辑运算符AND或OR连接的一组查询条件。
逻辑后缀字段的类型为集合或者查询对象,用于构建多个查询条,其中每个元素或字段对应一个查询条件。
逻辑后缀字段的名称中包含逻辑后缀And/Or,用于指定连接多个查询条件的逻辑运算符。
通过子查询字段构建子查询条件。子查询字段的类型需要为查询对象。
例如子查询条件age > (SELECT avg(age) FROM t_user [WHERE])
,我们可以将其分为三个部分分别进行映射:
- 对于条件部分age >,我们可以复用谓词后缀字段的映射方法进行构建。但是,为了避免和原有的谓词后缀字段ageGt产生命名冲突,我们需要在谓词后缀后再加一些字符进行区分,例如ageGtAvg。在构建时,忽略谓词后缀后的额外字符;
- 对于子查询的主句部分
SELECT avg(age) FROM t_user
:通过注解声明列名和表名,例如@Subquery(select = "avg(age)", from = "t_user")
,或者定义在字段名称中,例如ageGtAvgAgeOfUser; - 对于子查询的WHERE子句部分:通过复用查询对象映射方法进行构建。
通过以上三种字段,我们可以完成大部分查询条件的构建。对于其他查询条件,我们可以继续提出新的方法进行支持。
示例
查询对象映射方法仅通过字段的元信息来构建对应的查询条件,因此可以适用于任何面向对象编程语言。我们以Java和Go语言的实现版本进行举例。
Java示例:
public class UserQuery { // WHERE Integer ageGt; // AND age > ? Integer ageLe; // AND age <= ? Boolean memoNull // AND memo IS [NOT] null String memoLike // AND memo LIKE ? Boolean valid; // AND valid = ? UserQuery userOr; // AND (age > ? OR age <= ? OR valid = ?) @Subquery(select = "avg(age)", from = "t_user") UserQuery ageGtAvg; // AND age > (SELECT avg(age) FROM t_user [WHERE]) }
Go示例:
type UserQuery struct { // WHERE PageQuery AgeGt *int // AND age > ? AgeLe *int // AND age <= ? MemoNull *bool // AND memo IS [NOT] null MemoLike *string // AND memo LIKE ? Valid *bool // AND valid = ? UserOr *[]UserQuery // AND (age > ? OR age <= ? OR valid = ?) // AND age > (SELECT avg(age) FROM t_user [WHERE]) ScoreGtAvg *UserQuery `subquery:"select:avg(age),from:t_user"` }
其中,每个字段对应一个或一组查询条件,根据字段的赋值进行组合,拼接为最终的查询子句。逻辑查询条件和子查询条件还能通过复用查询对象进行构造。这两个优势是SQL作为静态语言所不具备的。
通过以上方式定义查询对象后,开发人员不再需要显示编写if语句拼接查询条件。框架可以通过反射技术读取每个字段的赋值,将每个查询参数的赋值判断和查询条件的拼接隐式得包含在框架代码里,从而大大简化了动态查询的代码编写和维护。
此外,上述查询对象还能用于构建MongoDB的查询语句:
{ "$and": [ {"age": {"$gt": {}}}, {"age": {"$lte": {}}}, {"memo": null}, {"memo": {"$regex": {}}}, {"valid": {"$eq": {}}}, {"$or": [{}, {}, {}]} ] }
(其中空对象类似于SQL中的占位符。)
由于所有的查询语言的设计都是基于同样的数学原理,所以我们基于这些数学原理设计的查询对象映射方法是可以适用于所有的面向对象编程语言和所有的数据库查询语言。
结论
在面向对象开发中,我们可以通过查询对象映射方法将查询对象构造为SQL语句中的查询子句。查询对象映射方法包括一种根据查询对象的字段赋值自动地将字段对应的查询条件动态组合为查询子句的方法,以及三种以上将字段构建为查询条件的方法,有效地简化了动态查询代码的编写和维护。
至于复杂查询语句中的聚合查询和连接查询,则可以通过一种视图对象映射方法进行构建。但是这已经不属于传统ORM的范畴了。我称之为对象查询映射(Object Query Mapping)。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
RocksDB 内存超限问题剖析
作者:来自 vivo 互联网服务器团队- Zeng Luobin 在使用 RocksDB 存储引擎的过程中,有部分开发者遇到了内存使用超出预期的情况。本文针对这一问题展开了深入分析,从内存使用原理、RocksDB 内存管理机制、常见内存使用问题等方面进行了详细探讨,并提出了相应的解决方案和优化建议,希望能够帮助开发者更好地理解和优化 RocksDB 的内存使用情况,提升系统性能和稳定性。 一、背景 1.1 前言 在现代数据库系统中,RocksDB 作为一种高性能的键值存储引擎,广泛应用于需要高吞吐量和低延迟的场景。然而,在使用过程中观察到 RocksDB 的内存使用常常超出预设的阈值,这一现象对系统的稳定性和可用性构成了严重威胁。 RocksDB 提供了通过 block-cache-size 参数来控制缓存使用的机制。开发者可以通过以下代码片段设置缓存大小: std::shared_ptr<rocksdb::Cache> cache = rocksdb::NewLRUCache(cache_size, -1, true); 左右滑动查看完整代码 然而,实际应用中发现,Roc...
- 下一篇
Gitee AI 助力医疗科研:医用耗材使用分析研究
本文作者:铂金小猪 我叫铂金小猪,万万没想到,我一个公立医院负责医疗耗材管理的野生程序员,有一天我会成为医疗界的「AI技术专家」(自己都觉得脸红)。上一篇文章《Gitee AI+Dify 双剑合璧,打造另类 RAG 知识库》中,我提到我们用 AI 技术参与了一个国家卫健委的科研项目。今天,我继续分享一下,我们是如何利用 AI 来参与到医用耗材管理工作里面的。 项目名称:2024医学工程研究项目 项目发起部门:国家卫健委医院管理研究所 课题名称:基于病种和临床路径的医用耗材使用管理和评价体系研究 课题单位:云南省第一人民医院、富源县人民医院 根据国家卫健委医院管理研究所的相关要求,基于病种和临床路径的医用耗材使用管理和评价体系研究项目的主要研究方向是,聚焦医院医用耗材精细化使用管理需求,研究建立基于病种和临床路径的医用耗材使用管理方法及评价体系,针对植介入重点管控医用耗材开展应用评价,优化医用耗材使用管理,服务于临床专科能力建设和医院高质量发展。 按照这一要求,我们对业务进行了分析,发现这事儿得要用 AI 的一些能力才能干完。 主要痛点分析 整个项目,基本就是围绕病历、结算数据去进行分...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS6,CentOS7官方镜像安装Oracle11G
- Mario游戏-低调大师作品
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7