先简单说说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. POJ 2186 Popular cows(Kosaraju+强联通分量模板)

    题目链接:http://poj.org/problem?id=2186 题目大意:给定N头牛和M个有序对(A,B),(A,B)表示A牛认为B牛是红人,该关系具有传递性,如果牛A认为牛B是红人,牛B认为 ...

  2. JavaScript 执行环境 与 变量对象

    什么是JS的执行环境? function funA(){ //一段代码静静的躺在这里,不能叫执行环境 } funA(); //当代码开始执行以后,系统会将它存入执行栈,并为他准备好足够的内存空间使用 ...

  3. 关于语义化版本(semantic versioning or SemVer)

    1  为什么要有SemVer? SemVer用来规范组件之间的依赖版本,它使用一个版本号来传递出组件的API的变化情况. 在理解这规范之后,看一眼依赖包的版本号,就知道API的变化(兼容性)程度,方便 ...

  4. PHP完整的AES加解密算法使用及例子(256位)

    依赖PHP自身的mcrypt扩展 <?php class aes { // CRYPTO_CIPHER_BLOCK_SIZE 32 private $_secret_key = 'default ...

  5. Tornado入门资料整理

    预备知识 没学过计网的苦逼找点现成一些的东西看吧…… <Restful Web Services>,<HTTP The Definitive Guide>,各种RFC WSGI ...

  6. Nuxt 2.0 需要将pug-loader改成pug-plain-loader

    Nuxt 2.0 需要将pug-loader改成pug-plain-loader npm i pug-plain-loader -D 解决问题!! 参考链接 https://my.oschina.ne ...

  7. lr_get_transaction_duration 函数介绍

    lr_get_transaction_duration 用于获取事务所消耗的时间. 实例: Action() { double trans_time; //定义变量 web_url("www ...

  8. Docker —几个概念的理解

    本文从一种使用场景来引出docker,并讨论了什么是镜像,容器,仓库,以及docker的相关概念. 试想一种使用场景: 我的wordpress 博客网站现在部署在阿里云服务器上,但是在后期的使用中我有 ...

  9. HDU 6185 Covering

    矩阵快速幂. 一开始的思路是$dfs$出一个矩阵,$k[i][j]$表示这一行是状态$i$,将这一行填满,下一行是$j$状态的方案数.然后就可以矩阵快速幂了,但是矩阵大小是$16*16$的,超时了.. ...

  10. axio post 请求后端接收不到参数的解决办法

    原因是没有对参数进行序列化 默认情况下,axios将JavaScript对象序列化为JSON. 要以应用程序/ x-www-form-urlencoded格式发送数据. 在拦截器前修改 方法一,用原生 ...