先簡單說說ORM的優點:
- 提高開發效率,減少重復勞動,只和業務實體打交道,由業務實體自動生成sql語句,不用手寫sql語句。
- 簡單易用, 可維護性好。
- 隔離數據源,使得我們更換數據源時不用修改代碼。
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,歡迎大家來交流技術。