先简单说说ORM的优点:

  1. 提高开发效率,减少重复劳动,只和业务实体打交道,由业务实体自动生成sql语句,不用手写sql语句。
  2. 简单易用, 可维护性好。
  3. 隔离数据源,使得我们更换数据源时不用修改代码。

SmartDB基础库

java和c#中有很多ORM框架,如c#中的entity framework、Linq to Sql、NHibernate等,java中有Hibernate、Mybatis等等,其它各种开源的ORM库多如牛毛。这应该得益于托管语言在这方面的优势。然而,c++的世界中,却鲜有ORM框架,c++中比较知名的ORM库应该是ODB了,但是ODB的使用比较麻烦,需要三个库,写的ORM代码还需要ODB专门编译一下(不知道我说的是否确切),总之使用起来比较麻烦,让人望而却步。我的项目中本来只需要用sqlite,但是考虑到后期可能会更换数据库,所以想搞一个ORM库,但时间又比较紧,觉得短时间内搞不定ODB,于是放弃,选择最简单稳妥的作法,先只封装sqlite,目标是封装成简洁,统一的接口,即使到后面更换数据库时,我也不需要改应用层的代码,只要替换底层数据库封装模块就行了。在这个目标的指引下我很快完成了基础库SmartDB的封装,如下是SmartDB的操作接口。

bool Open(string dbName);//打开数据库
bool Close();//关闭数据库
bool Excecute(const std::string && query); //直接执行sql语句
bool ExcecuteBulk(const Args && ... args); //批量操作的sql语句
bool Prepare(const std::string && query); //批量操作之前的准备
bool Excecute(const std::string && query, const Args && ... args); //执行带占位符的sql语句
R ExecuteScalar(const std::string &query, const Args &... args); //返回函数执行的一个值, 执行简单的汇聚函数,如select count(*), select max(*)等
bool GetTable(const string &query, T &table, TableRange* tableRange, const Args &... args); //返回一个通用的表结构
int GetRowCount(const string &query, const TableRange* tableRange, const Args &... args);//获取表的总行数

这些接口是直接接受sql语句和参数列表的,所以理论上它可以完成所有的数据库操作。目前内部只支持sqlite,我自测了一下基础库的性能。在我的双核6G的台式机下测试了一下插入速度和读取速度,一个八字段的表(int和doble类型),批量插入速度大概为25W条/s,读取速度大概为11W条/s。这些已经满足了我的需求,但是我觉得可以在这个基础上做得更多,即支持ORM,我期望我的ORM能达到类似于entity framework的调用方式,比ODB更好用(比如智能提示、链式调用等),而性能又不至于降低太多。

EF中查询方式
DemoDBEntities context = new DemoDBEntities();
ObjectQuery<BlogMaster> query = context.CreateObjectSet<BlogMaster>().Where("it.UserId > @UserId",new ObjectParameter("UserId", 6))
.OrderBy("it.UserId desc"); List<BlogMaster> list = query.ToList();

SmartDBEX ORM库

c++中ORM库的实现思路: ORM的关键是如何将业务实体转成sql语句以及表的原始数据如何转成业务实体,它们横在数据库和实体之间的两道鸿沟,因此,如何通过一个结构体去自动生成sql脚本是第一个需要解决的问题,如何将原始表转成业务实体是第二个需要解决的问题。我觉得问题的关键在结构体,我需要结构体提供一些信息给我,比如,字段类型,字段名,字段值等信息,这在c#或者java中可以通过反射就可以得到,但c++没有反射,是无法自动就得到这些信息的,不过C++虽然没有托管语言的这些优势,但却有自己的优势--模板元,可以通过模板元来获取这些信息,通过这些信息我就可以自动生成sql脚本了;第二个问题则是通过boost的variant和结构体中的赋值函数来解决。

总之,我通过在结构体上做文章,最终达到了我的目标,而且我会保证结构体不会复杂,尽可能少的让用户去写代码,只需要提供几个简单的函数就可以实现ORM了。我是在基础库SmartDB之上扩展了ORM功能,最终的ORM库为SmartDBEX, 它内部是调用了SmartDB实现具体功能。这样做的目的是即可使用操作sql的接口,又可使用ORM接口,用户根据自己选择即可。

我在台式机上测了一下SmartDBEX的性能:还是之前的表,批量插入速度为25W条/s,读取并转成实体列表的速度为2.3W条/s。比较满意的是批量插入速度没有降低,差强人意的是读取实体列表的速度降低了不少,性能损耗在两个地方:原始表的字段和实体的字段匹配和原始表字段赋值给实体。没办法,ORM这里必须要损耗一些性能。2.3W条/s的读取速度对一般应用来说已经可以满足要求了。

如何使用SmartDB和SmartDBEX

SmartDB的测试程序

/*创建表*/
void TestCreateTable(SmartDB& db)
{
const string sqlcreat = "CREATE TABLE if not exists TestInfoTable(ID INTEGER NOT NULL, KPIID INTEGER, CODE INTEGER, V1 INTEGER, V2 INTEGER, V3
REAL);";
if (!db.Excecute(std::move(sqlcreat)))
return;
} /*单条插入*/
void TestInsert(SmartDB& db)
{
const string sqlinsert = "INSERT INTO TestInfoTable(ID, KPIID, CODE, V1, V2, V3) VALUES(1, 2, 3, 4, 5, 6.032);";
if (!db.Excecute(std::move(sqlinsert)))
return; const string sqlinsert1 = "INSERT INTO TestInfoTable(ID, KPIID, CODE, V1, V2, V3) VALUES(?, ?, ?, ?, ?, ?);";
int n = 2;
string str = "3.1423";
if (!db.Excecute(std::move(sqlinsert1), std::move(n), std::move(n), std::move(n), std::move(n), std::move(n), std::move(str)))
return;
} /*批量插入, 启用事务*/
void TestBulkInsert(SmartDB& db)
{
const string sqlinsert = "INSERT INTO TestInfoTable(ID, KPIID, CODE, V1, V2, V3) VALUES(?, ?, ?, ?, ?, ?);";
timer t;
Transaction transaction(&db); //开始事务
db.Prepare(std::move(sqlinsert)); //准备sql,含占位符
bool ret = false;
for (size_t i = 0; i < 1000000; i++)
{
ret = db.ExcecuteBulk(std::move(i), std::move(i), std::move(i), std::move(i), std::move(i), std::move(i));
if (!ret)
break;
} if (ret)
transaction.Commit(); //提交事务
else
transaction.RollBack(); //回滚 cout << t.elapsed() << endl;
}

SmartDBEX的测试程序

void TestORM(SmartDB& db)
{
SmartDBEx dbex(db);
//创建表
auto r = dbex.Create<TestInfo>(); //查询
auto& query = dbex.Query<TestInfo>().Where(Var(TestInfo::CODE < 10 and TestInfo::ID < 10));
vector<TestInfo> v;
bool b = dbex.Get<TestInfo>(v); //获取总行数
int count = dbex.Count<TestInfo>().Scalar();
cout << count << endl; //单条插入
r = dbex.Insert(t); //批量插入
TestInfo t = {1,2,3,4,5,6};
Transaction tans(&db);
r = dbex.Prepare<TestInfo>();
for (size_t i = 0; i < 1000000; i++)
{
r = dbex.BulkInsert(t);
}
r = tans.Commit(); //删除
r = dbex.Delete<TestInfo>()();
}

这里我们不妨对比一下ODB的调用方式,ODB的查询方式:

typedef odb::query<person> query;
typedef odb::result<person> result; {
transaction t (db->begin ()); result r (db->query<person> (query::age > 30)); for (result::iterator i (r.begin ()); i != r.end (); ++i)
{
cout << "Hello, " << i->first () << "!" << endl;
} t.commit ();
}

SmartDBEX的查询方式:

auto& query = dbex.Query<TestInfo>().Where(Var(TestInfo::CODE < 10 and TestInfo::ID < 10));
vector<TestInfo> v;
bool b = dbex.Get<TestInfo>(v);

呵呵,有点像吧,但哪个更直观简洁呢?

一些遗憾

牺牲了好几个周末的时间做这个ORM,主要是ORM的关键技术的思路和实现花了不少时间,今天终于做完了第一个版本,算是对前段时间的思考和研究做个总结和交代吧。不过由于是业余时间开发,时间和精力有限,ORM接口目前只有一些基本功能,还不太完善,而且目前内部只支持sqlite,至于其它数据库的支持,等到后面再继续完善了。另外,库内部没有采用缓存和延迟加载等技术,不是不能加,是暂时没有时间和精力去继续完善了,而且性能已经满足我的要求了,优化完善的工作来日方长吧;后期还可以考虑支持结构体通过文件配置的方式生成,进一步增强灵活性。

欢迎使用

欢迎大家下载测试工程和源码并试用,发现问题请报告给我,我好完善。也期待有人愿意在此基础上和我一起完善它,使它成为一个优秀的c++ORM库,让c++开发人员的日子变得更美好,让c++的世界更加绚丽多彩。

源码下载地址

发布一个C++版本的ORM库SmartDB的更多相关文章

  1. (原创)发布一个C++版本的ORM库SmartDB(一)

    先简单说说ORM的优点: 提高开发效率,减少重复劳动,只和业务实体打交道,由业务实体自动生成sql语句,不用手写sql语句. 简单易用, 可维护性好. 隔离数据源,使得我们更换数据源时不用修改代码. ...

  2. 一个C++版本的Sqlite3封装--SmartDb

    Sqlite是一个非常轻量级的开源数据库,在嵌入式系统中使用的比较多,存储管理数据非常方便,Sqlite库提供的基于C语言的API,用起来也挺简单,但是有一点不太好的就是API使用起来有些繁琐,另外就 ...

  3. 发布一个简单的knockout-easyui绑定库

    最近做事情总是南辕北辙,拖延症越发严重了起来.原先计划早就要完成的这个项目也拖延了近两个月后总算勉勉强强发布了(最开始设想的部分功能就这么砍了,好吧纯粹个人太懒) knockout作为老牌的mvvm框 ...

  4. Node的关系型数据库ORM库:bookshelf

    NodeJs 关系数据库ORM库:Bookshelf.js bookshelf.js是基于knex的一个关系型数据库的ORM库.简单易用,内置了Promise的支持.这里主要罗列一些使用的例子,例子就 ...

  5. 教你一步步发布一个开源库到 JCenter

    今天想来分享下,如何一步步自己发布一个开源库到 JCenter 这方面的博客网上已经特别多了,所以本篇并不打算仅仅只是记录流程步骤而已,而是尽可能讲清楚,为什么需要有这个步骤,让大伙知其然的同时还知其 ...

  6. 深度学习库 SynapseML for .NET 发布0.1 版本

    2021年11月 微软开源一款简单的.多语言的.大规模并行的机器学习库 SynapseML(以前称为 MMLSpark),以帮助开发人员简化机器学习管道的创建.具体参见[1]微软深度学习库 Synap ...

  7. (原创)发布一个c++11开发的轻量级的并行Task库TaskCpp

    TaskCpp简介 TaskCpp是c++11开发的一个跨平台的并行task库,它的设计思路来源于微软的并行计算库ppl和intel的并行计算库tbb,关于ppl和tbb我在前面有介绍.既然已经有了这 ...

  8. 发布 Vant - 高效的 Vue 组件库,再造一个有赞移动商城也不在话下

    发布 Vant - 高效的 Vue 组件库,再造一个有赞移动商城也不在话下:https://segmentfault.com/a/1190000011377961 vantUI框架在vue项目中的应用 ...

  9. Spring 5的最后一个特性版本5.3发布,4.3将于12月终止维护

    10月27日,Spring Framework团队宣布了5.3版本正式GA,Spring用户可以在repo.spring.io和Maven Central上获取到最新版本的依赖包. JDK的版本支持 ...

随机推荐

  1. initialization 与 finalization 执行顺序 研究

    看GIF: 第二个GIF: DEMO下载:http://files.cnblogs.com/files/del88/InitOrderDemo.zip

  2. HDU 2112 Today(Dijkstra+map)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2112 题目大意: 经过锦囊相助,海东集团终于度过了危机,从此,HDU的发展就一直顺风顺水,到了2050 ...

  3. Java容器---Arrays & Collections工具类

    1.Array & Arrays 与Collection & Collections区别 (1)Collection": 是一个接口,与其子类共同组成一个Collection ...

  4. 安装 jupyter notebook 出现 ModuleNotFoundError: No module named 'markupsafe._compat' 错误

    使用 python -m pip install jupyter 安装完成 jupyter notebook 之后,在命令行界面输入 "jupyter notebook "指令打开 ...

  5. Loadrunner中cookie解释与用法

    loadrunner对于cookie的处理loadrunner中与cookie处理相关的常用函数如下: web_add_cookie():添加新的cookie或者修改已经存在的cookie web_r ...

  6. Python教程(一)Python简介

    Python就为我们提供了非常完善的基础代码库,覆盖了网络.文件.GUI.数据库.文本等大量内容,被形象地称作“内置电池(batteries included)”.用Python开发,许多功能不必从零 ...

  7. 洛谷P3444 [POI2006]ORK-Ploughing [枚举,贪心]

    题目传送门 ork 格式难调,题面就不放了. 分析: 一道偏难的贪心和枚举题.考试的时候是弃疗了...yyb巨佬已经讲的很详细了,推荐他的博客.这里小蒟蒻就只放代码了. Code: #include& ...

  8. shell sh bash 概念

    在shell脚本的开头往往有一句话来定义使用哪种sh解释器来解释脚本.目前研发送测的shell脚本中主要有以下两种方式:(1) #!/bin/sh(2) #!/bin/bash以上两种方式有什么区别? ...

  9. esxi上引起vm绑定浮动IP无法和外面通信

    在vmware esxi环境通过创建VM安装完成openstack之后,发现创建Instance后网络不通,经过多方面排查,最后确定是vmware esxi标准交换机拒绝“混杂模式”所致,故打开“混杂 ...

  10. 【UOJ #206】【APIO 2016】Gap

    http://uoj.ac/problem/206 对于T=1,直接从两端往中间跳可以遍历所有的点. 对于T=2,先求出最小值a和最大值b,由鸽巢原理,答案一定不小于\(\frac{b-a}{N-1} ...