文章目录
网上针对C++优秀的ORM框架很多,但大都保留了原始使用方式,而并非从用户的角度出发去实现和使用,本文根据用户使用角度来设计和实现全新的ORM。
注意:本文只说明yan_db的一些使用方式,作为github的辅助,如果相关人员感兴趣整个实现原理,可以自行从github下载源码进行分析,源码地址:https://github.com/stxrlong/yan_db。
前置说明
如果你希望使用某一个数据库,但是面对复杂的数据库使用语句又很纠结,那么这篇文章可能比较适合你,耐心读完可能对你有些许的帮助,因为遇到问题第一时间是找已经踩过坑善良人帮我解决问题是一种优良的“互联网精神品质”,原谅我写到这里已经不好意思的笑了。
先讲些废话,18年的时候有个项目需要用到C++访问MySQL,但是看到代码实现有点惊讶,为了在C++侧的SQL语句解析和Result回包解析不影响业务的扩展实现(实际上扩展也很难),项目里采用了Json来做中间体,先将业务的所有数据写入到Json,然后通过Json的一些特性来组装写入和查询的SQL,因为Json里面还可以带上一些特殊字符串,如“>”, “<”等,用于组装SQL语句,并且一个对象里面可以写入各种类型的数据,这满足了基本使用,也理解当时的源码作者在简单的实现反射的能力,但是无法实现复杂逻辑,而作为牺牲是代码的复杂度极大提高和运行性能极大地损失。
到了19年自己在设计一个项目时想用MongoDB,但是看到MongoDB复杂的查询语句就有点退怯,光学习这块内容就非常的痛苦。我比较喜欢拿来主义,就在同性网站上查询了很久,发现了一些非常优秀的C++的结构性ORM框架,比如LiteSQL、TinyORM等,但针对MongoDB这类nosql的支持比较少(当然他不是结构性数据库)。在分析了这些ORM的设计之后,也发现他们万变都不离依附于原生语句的实现,比如处处透着用户必须知道原生SQL语句的实现才能使用这些开源库。作为高级懒人,我最希望一劳永逸的操作:即有没有一种所见即所得的方式来操作数据库,不要让我知道那么复杂的查询逻辑和语句,我只希望对我业务设计的表结构进行操作,不希望操作针对这些表结构还有很多的底层实现。因此在那时我设计了这个在C++语言上统一ORM的实现。
到20年在一个项目中使用了这个C++的ORM框架后就一直比较忙,没有空去优化一些缺点,直到最近开始不得已使用redis了,就又拿出来炒炒冷饭,并且将冷饭炒好了进行分享以供给有需之同学取用。
我们通过讲解上述提到的几种代表性数据库来讲解这个yan-db的使用,他们分别是MySQL(结构化数据库)、MongoDB(nosql的数据库)、Redis(固化表结构的数据库)。
注意:由于内容比较多,我们可能需要分几篇文章分别来讲述这些数据库在C++侧如何做到上述两点能力。
结构化数据库(以MySQL为例)
假设针对一个项目的数据库类别们建立了一种结构化数据存储类别的资产信息,如下表所示,记录了某个产品使用了哪些类型的数据库,在什么端使用,主要存储什么类别的数据,什么时间开始部署等等信息:
| id | name | type | point | data | online |
|---|---|---|---|---|---|
| 0 | MySQL | structured | server | manager | 20210102 |
| 1 | SQLite | structured | client | tmp | 20210312 |
| 2 | MongoDB | nosql | server | history | 20220605 |
| 3 | Redis | K-V | server | cache | 20220515 |
这样一张表对于用户而言是非常清晰的,最好的方式是我们定义这样一个结构体:
struct MaintainDB {
int32_t id;
std::string name;
enum UsageType utype; // structured/nosql/k-v
enum PointType ptype;
enum DataType dtype;
int32_t online_date;
}
那么对于用户使用底层数据库而言,他熟悉的是上面这个MaintainDB结构,也知道底层用了什么类型的数据库(但是可能需要全新的学习数据库的调用方式),毕竟不同的数据库存储的性能,规模等都不一样,有个选型过程,但是这个选型过程可能经历了几个痛点:
- 我是一个新手,我对数据库完全不了解,只能通过互联网资料分析再结合当前业务进行选型;
- 选型之后发现业务上线要求快,临时学习又很难受,赶紧找现成的工具处理,如果没有合适的,那就选择自己熟悉的数据库先完成业务,再考虑后面的修改;
- 深入开发后,发现熟悉的数据库要满足一些业务,还得做一些处理,因为官方不支持,得造些轮子。
这也是我们常常把项目延期的一些微小的问题问题(当然,数据库只是一个小点,更多的还是在于方案没有思考清楚就开始写代码,不断写不断删),自以为项目紧,把方案和选型时间压缩,让bug收敛的时间无限且不可知地延长。
回过头来,那么如果我们在使用上对于数据的写入操作就如下面这么简单:
MaintainDB obj;
// 用户的赋值操作;
obj.name = “MongoDB”
obj.utype = UsageType::nosql;
obj.ptype = PointType::server;
obj.dtype = DataType:history;
obj.online_date = 20220605;
db.write(obj);
对于查询操作也希望屏蔽一些SQL语句,让查询的动作就跟用户的思维方式是一样的:
MaintainDB obj;
obj.name.AND() == “MySQL”;
std::list<MaintainDB> ret = db.read<std::list<MaintainDB>>(obj);
如果查询条件有两个:
MaintainDB obj;
obj.name.AND() == “MySQL”;
obj.online_date.AND() >= “20220601”; // 直接显示化的让用户思维在代码层面体现出来
std::list<MaintainDB> ret = db.read<std::list<MaintainDB>>(obj);
在这种情况下,使用都是建立在用户自己的业务上面的,不涉及技术层面的复杂逻辑(我们并不知道select、from、where等SQL语句,用户只有业务设计的表结构),如果能达到这种效果,那么这个ORM就对于我们这类懒人而言是非常友好的操作了(不仅代码简单易读,使用上也就减少一环了学习成本带来的压力)。事实上,我一直在纳闷开发数据库的那些厂商为啥就不能这么搞一把呢?当然了,实际使用场景肯定是非常复杂的,尤其是对于复杂的查询场景而言。
不管哪种数据库,让ORM最痛苦的就是查询操作,所以我们在讲具体实现时主要围绕查询功能来说明。对于传统的结构化数据库而言,主要是SQL语句的组装,如MySQL、SQLite等数据库,主要是两大难点:
- 在早几年,为保障安全能力,SQL语句查询尽量采用preparestatement能力防止数据库被脱裤或者拒绝服务(比如where 1=1这种,我记得还是很多厂商的安全面试题之一);(
这里这部分具体实现请阅读源码自行了解,本文仅介绍简单的使用方式) - 多表联合查询;
对于write操作是比较简单的,上述已经分析过了,这里不再赘述了,主要讲一下查询和更新操作,事务能力源码还没有实现(作者会尽快补上),有兴趣读者可以按照本文理解或者阅读源码提供pull request。
单表查询
首先我们由浅入深,讲一下单表查询,当然结构化数据库对于实现而言也不是仅增加难度,也有简单的地方:就是结构化数据库只有一维结构(如果是要实现多维,只能建多张表实现,也就是存在多表联合查询问题),不存在单表多维结构,而MongoDB的nosql,采用的bson结构类似json,因此一个表就可以存储所有维度数据。
在结构化数据库的查询实现上我们可以总结查询条件不外乎如下几种(有更高级的使用方法,请读者及时联系本文作者更正):
- 具体字段值条件,例如上述查询name为MySQL的数据条;
- 具体字段的范围条件,例如上述查询的online_date要求是大于或者等于20220601上线的数据库;
- 具体字段的组合条件(即group by的能力);
- 具体字段的排序条件(即order by的能力);
- 查询条件的起始位置和条数(即offset和limit能力),通常用于翻页;
那么在这种实现方式下,我们赋予结构体每个字段具有这种条件设置的能力就可以实现查询过程,并且这种设置能力是符合用户的业务认知的,例如上述online-date的范围赋值操作是直接采用比较符号“>=”的直观感知操作(我们在这基础上重载了赋值能力),而不是常规的“=”符号赋值。
上述查询条件基本都是赋值操作,范围维度的操作会有多种场景存在:
- 查询多个值条件 比如希望查询name为MySQL和MonogoDB这两种值,那么我们可以采用如下方式:
MaintainDB obj;
obj.name.AND() == std::initialized_list<std

最低0.47元/天 解锁文章
925

被折叠的 条评论
为什么被折叠?



