acl 的 C++ 版本庫(lib_acl_cpp.a)的 db 模塊主要與數據庫編程相關,經過這些模塊庫,開發者能夠快速地寫出支持數據庫鏈接池的數據庫應用程序,目前該 db 模塊支持 mysql、sqlite 數據庫。本文將以 mysql 應用爲例講述如何使用這些 API 接口編程數據庫應用。mysql
在 lib_acl_cpp/include/acl_cpp/db 目錄下,能夠看到主要分三個部分:數據庫操做句柄類(db_handle,db_mysql,db_sqlite)、數據庫鏈接池類(db_pool,mysql_pool,sqlite_pool)及數據庫服務類(db_service,db_service_mysql,db_service_sqlite,這些類主要用在阻塞非阻塞結合的應用中,如:MFC界面過程與數據庫過程的結合,非阻塞 IO 過程與數據庫過程結合)。sql
1、數據庫操做句柄數據庫
下圖顯示了數據庫句柄的類繼承關係:db_handle 爲基礎類,db_mysql/db_sqlite 類均繼承於 db_handle 類。編程
在 db_mysql.hpp/db_sqlite.hpp 兩個頭文件中能夠看出,這兩個子類僅是實現了基礎類 db_handle 的一些虛函數而已,大量關於數據的操做函數都集中於 db_handle.hpp 頭文件中,下圖爲 db_handle 類的功能協做圖(其中的 db_rows/db_row 兩個類爲數據庫查詢結果類):後端
下面給出了一個簡單的數據庫查詢示例:數組
//////////////////////////////////////////////////////////////////////////////// /** * 從數據庫中查詢表數據 * @param db {acl::db_handle&} acl 中的數據庫鏈接句柄引用 */ static void tbl_select(acl::db_handle& db) { // 建立 sql 查詢語句 const char* sql = "select * from group_tbl where" " group_name='test_name' and uvip_tbl='test'"; // 查詢數據庫 if (db.sql_select(sql) == false) { printf("select sql: %s error\r\n", sql); return; } printf("\r\n---------------------------------------------------\r\n"); // 列出查詢結果方法一:從數據庫句柄中得到查詢結果集合 const acl::db_rows* result = db.get_result(); if (result) { // 遍歷查詢結果集 const std::vector<acl::db_row*>& rows = result->get_rows(); for (size_t i = 0; i < rows.size(); i++) { const acl::db_row* row = rows[i]; // 打印一行結果中的全部結果 for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } // 列出查詢結果方法二:根據數組下標遍歷數據庫句柄的查詢結果集 for (size_t i = 0; i < db.length(); i++) { const acl::db_row* row = db[i]; // 取出該行記錄中某個字段的值 const char* ptr = (*row)["group_name"]; if (ptr == NULL) { printf("error, no group name\r\n"); continue; } printf("group_name=%s: ", ptr); for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } // 列出查詢結果方法三:直接從數據庫句柄中得到結果數組 const std::vector<acl::db_row*>* rows = db.get_rows(); if (rows) { std::vector<acl::db_row*>::const_iterator cit = rows->begin(); for (; cit != rows->end(); cit++) { const acl::db_row* row = *cit; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } // 必須釋放查詢結果 db.free_result(); } //////////////////////////////////////////////////////////////////////////////// /** * 打開 mysql 數據庫鏈接句柄 * @return {acl::db_handle*} 返回值爲 NULL 表示鏈接數據庫失敗 */ static acl::db_handle* open_mysql(void) { const char* dbaddr = "127.0.0.1:3306"; const char* dbname = "acl_test_db"; const char* dbuser = "acl_user", *dbpass = "111111"; acl::db_handle* db = new acl::db_mysql(dbaddr, dbname, dbuser, dbpass); if (db->open() == false) { printf("open mysql db error\r\n"); delete db; return NULL; } return db; } /** * 打開 sqlite 數據庫句柄 * @return {acl::db_handle*} 返回值爲 NULL 表示鏈接數據庫失敗 */ static acl::db_handle* open_sqlite(void) { const char* dbfile = "test.db"; acl::db_handle* db = new acl::db_sqlite(dbfile); if (db->open() == flase) { printf("open mysql db error\r\n"); delete db; return NULL; } return db; } //////////////////////////////////////////////////////////////////////////////// void db_demo(void) { acl::db_handle* db; // 操做 mysql 數據庫過程 db = open_mysql(); if (db) { tbl_select(*db); delete db; } // 操做 sqlite 數據庫過程 db = open_sqlite(); if (db) { tbl_select(*db); delete db; } }
從上面的例子能夠看出,雖然操做的數據庫不一樣,但數據庫查詢方式倒是徹底同樣的,由於 acl 類內部屏蔽了數據庫操做的差別性。下面還有幾點須要注意:服務器
1)對於數據庫查詢結果集有多種操做方式,開發者能夠根據須要進行選擇;網絡
2)其中生成的 sql 查詢語句比較簡單,因此沒有作特殊字符轉義,真正生產環境中開發者應注意對 sql 中的一些變化查詢字段進行轉義(可使用 acl::db_handle 類中的 escape_string 方法),以防止 sql 注入攻擊;svn
3)若是查詢的數據庫結果集非空,則在處理結果完畢畢竟調用 acl::db_handle 類中的 free_result() 方法釋放中間動態分配的內存;函數
4)在使用 acl 數據庫類編寫代碼時不須要包含 mysql 和 sqlite 的頭文件,但在程序鏈接階段必須將 mysql/sqlite 的靜態庫加上。
2、數據庫鏈接池
爲了不創建數據庫鏈接開銷對數據形成衝擊,通常的數據庫鏈接都建議使用鏈接池方式(尤爲是在JAVA、PHP等應用中);鏈接池在保持與數據庫的長鏈接過程當中,必需要處理鏈接中斷的重連狀況,使上層使用者忽略鏈接中斷的狀況。
下圖爲 acl 的數據庫鏈接池中各種的繼承關係及鏈接池基礎類的函數接口:
從 mysql_pool.hpp/sqlite_pool.hpp 頭文件中能夠看出,兩者的主要區別是構造函數略有不一樣:
db_pool 類爲數據庫鏈接池基類,其中主要有兩個方法:
/** * 從數據庫中鏈接池得到一個數據庫鏈接,該函數返回的數據庫 * 鏈接對象用完後必須調用 db_pool->put(db_handle*) 將鏈接 * 歸還至數據庫鏈接池,由該函數得到的鏈接句柄不能 delete, * 不然會形成鏈接池的內部計數器出錯 * @return {db_handle*} 返回空,則表示出錯 */ db_handle* peek(); /** * 將數據庫鏈接放回至鏈接池中,當從數據庫鏈接池中得到鏈接 * 句柄用完後應該經過該函數放回,不能直接 delete,由於那樣 * 會致使鏈接池的內部記數發生錯誤 * @param conn {db_handle*} 數據庫鏈接句柄,該鏈接句柄能夠 * 是由 peek 建立的,也能夠單獨動態建立的 * @param keep {bool} 歸還給鏈接池的數據庫鏈接句柄是否繼續 * 保持鏈接,若是否,則內部會自動刪除該鏈接句柄 */ void put(db_handle* conn, bool keep = true);
mysql 數據庫鏈接池的構造函數以下:
/** * 採用 mysql 數據庫時的構造函數 * @param dbaddr {const char*} mysql 服務器地址,格式:IP:PORT, * 在 UNIX 平臺下能夠爲 UNIX 域套接口 * @param dbname {const char*} 數據庫名 * @param dbuser {const char*} 數據庫用戶 * @param dbpass {const char*} 數據庫用戶密碼 * @param dblimit {int} 數據庫鏈接池的最大鏈接數限制 * @param dbflags {unsigned long} mysql 標記位 * @param auto_commit {bool} 是否自動提交 * @param conn_timeout {int} 鏈接數據庫超時時間(秒) * @param rw_timeout {int} 與數據庫通訊時的IO時間(秒) */ mysql_pool(const char* dbaddr, const char* dbname, const char* dbuser, const char* dbpass, int dblimit = 64, unsigned long dbflags = 0, bool auto_commit = true, int conn_timeout = 60, int rw_timeout = 60);
下面以 mysql 爲例寫一個簡單的使用鏈接池的函數:
void dbpool_demo(void) { const char* dbaddr = "127.0.0.1:3306"; const char* dbname = "acl_test_db"; const char* dbuser = "acl_user", *dbpass = "111111"; acl::db_pool* dbp = new acl::mysql_pool(dbaddr, dbname, dbuser, dbpass); // 建立 mysql 鏈接池 acl::db_handle* dbh = dbp->peek(); // 從鏈接池中獲取一個數據庫鏈接 if (dbh == NULL) { printf("peek db connection error\r\n"); delete dbp; return; } tbl_select(*dbh); // 從數據庫中查詢數據(使用上面的查詢例子) dbh->put(dbh); // 歸還數據庫鏈接給鏈接池 delete dbh; // 刪除鏈接池對象 }
由上面示例能夠看出 acl 中的數據庫鏈接池仍是比較簡單易用的,不過須要注意如下幾點:
1)在建立數據庫鏈接池對象時並不馬上鍊接後端的數據庫,數據庫的鏈接過程通常發生在 acl::db_pool::peek() 過程,但在調用 peek 時若是鏈接池有可用鏈接則直接使用之;
2)在使用數據庫鏈接操做數據庫時,若是由於網絡意外致使鏈接斷開,內部會根據數據庫鏈接的返回錯誤號決定是否須要重試該數據庫操做;
3)在用完數據庫鏈接後須要調用 acl::db_pool::put() 過程歸還數據庫連;
4)在編譯 lib_acl_cpp 庫時必須須要指定 Makefile.db 爲工程文件(make -f Makefile.db),這樣才能使 lib_acl_cpp.a 庫內部的數據庫功能生效;同時在編譯本身的應用程序時必須指定 libmysqlclient_r.a 的連接位置。
好了,關於如何使用 acl 庫編寫數據庫應用先寫到此,歡迎讀者批評指正。
其它有關數據庫使用例子請參考:acl\lib_acl_cpp\samples\mysql,acl\lib_acl_cpp\samples\sqlite。