先简单说说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. ueditor 编辑器上传到服务器后图片上传不能正常使用

    网站集成ueditor编辑器后在本地能正常使用,上传到服务器上后,图片上传功能提示:后端配置项没有正常加载,上传插件不能正常使用.且单个图片上传图标是灰色的不能点击. 相信遇到这个问题的同学是很多的吧 ...

  2. 配置OpenCV+VS2013环境

    配置OpenCV+VS2013环境 准备工作 win7系统 下载opencv的windows编译版 安装vs2013 express 设定环境变量 按windows窗键输入path,选择第二个结果编辑 ...

  3. 百度地图sdk定位和遇到的坑

    封装定位服务类: import android.content.Context; import com.baidu.location.BDAbstractLocationListener; impor ...

  4. 常用的scrapy setting

    原文请参考    Scrapy 爬虫入门教程十三 Settings(设置), 讲的很详细 官网参考  Settings 设置 Scrapy 设置允许您自定义所有 Scrapy 组件的行为,包括核心,扩 ...

  5. php将长字符串拆分为指定最大宽度的字符串数组

    /** * 将字符串拆分为指定最大宽度的字符串数组.单字节字符宽度为1,多字节字符通常宽度为2 * @param string $msg 要拆分的字符串 * @param int $width 结果数 ...

  6. bzoj 1131 简单树形dp

    思路:随便想想就能想出来啦把...  卡了我一个vector... #include<bits/stdc++.h> #define LL long long #define fi firs ...

  7. QString

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://devbean.blog.51cto.com/448512/275360 这段时间 ...

  8. 洛谷P2730 魔板 [广搜,字符串,STL]

    题目传送门 魔板 题目背景 在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板.这是一张有8个大小相同的格子的魔板: 1 2 3 4 8 7 6 5 题目描述 我们知道魔板的每一个方格都有 ...

  9. Java常用工具类之压缩解压

    package com.wazn.learn.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream ...

  10. BZOJ1552/3506 [Cerc2007]robotic sort

    Splay 与之前不同的是如果你仅仅是翻转左右区间的话可以在find里面做因为对他有影响的子树在做之前一定在他的上面从上到下搜索的过程可以把rever做了. 但这道题要求我们输出转换之前的,因此不能保 ...