(原創)發布一個C++版本的ORM庫SmartDB(一)


先簡單說說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(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(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(sqlinsert1, n, n, n, n, n, 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(sqlinsert); //准備sql,含占位符
bool ret = false;
for (size_t i = 0; i < 1000000; i++)
{
ret = db.ExcecuteBulk(i, i, i, i, i, 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++11 boost技術交流群:296561497,歡迎大家來交流技術。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM