一、概述
在我參與的多個項目中,大家使用libMySQL操作MySQL數據庫,而且是源碼即復用,在多個項目中有多套相同或相似的源碼,這樣的復用方式給開發帶來了不變,而且libMySQL的使用比較麻煩,要應對很多的細節,很容易出錯。
我要寫一個動態鏈接庫,將對libMySQL的操作封裝起來,以二進制復用代替源碼級復用;要提供線程安全的接口,用戶無需關系是否加鎖這樣細節性的問題,減少出錯及死鎖的機會,當然也要允許用戶自己選擇是否線程安全的訪問數據庫;要簡化訪問數據庫的流程,接口越簡單越好。
我從2011年開始寫這個庫,我給它起名字叫HiDB。HiDB從2011年到現在,經歷了一個由簡到繁又有繁到簡的過程,中間也包含了自己對程序理解的變化。
很多的考慮看起來很細碎,例如代碼的注釋風格(選擇我們一貫的注釋風格還是doxygen的注釋風格,代碼大全中也講到了注釋風格。是否需要借鑒);代碼的命名規范(匈牙利命名法還是Java,C#的命名法,還有很多開源項目的命名規范);C++設計新思維中講到Policy classes,是否可以應用到這個庫中;Effective C++中講的接口與實現分離。
這些年自己考慮過的一些東西包括注釋風格的選擇(我們一貫的注釋,doxygen的注釋風格,代碼大全中也講到了注釋風格),出錯時是使用錯誤碼報出錯誤還是使用異常報出錯誤?C++設計新思維中講的Policy classes,Effective C++中講的接口與實現分析(Impl)
二、接口
(一)接口概述
首先確定一下HiDB的接口。該庫對外顯示為一個HiDB類,該類應該包含的接口包括:
1: 用戶選擇線程安全還是非線程安全
2: 打開數據庫連接
3: 關閉數據庫連接
4: 執行insert,update等非查詢操作的接口(ExecuteNoQuery)
5: 獲得查詢結果第一行第一列的接口(ExecuteScaler)
6: 獲得一次查詢的所有記錄(多行多列)的接口(ExecuteQuery)
7: 執行事務的接口(OnTransaction)
所有的接口都在產生異常時拋出,需要對異常做一個封裝,這個異常中最好能標識出異常產生的位置,異常的編號,異常的描述,引起異常的SQL語句。我設計的異常如下:
/** @brief 數據庫操作異常 */ class HI_DB_EXPORT HiDBException: public std::exception { public: HiDBException(); public: std::string ToSrting(); const char* what() const; public: std::string m_sql; /**< 本次操作的SQL語句 */ std::string m_descript; /**< 異常描述 */ std::string m_position; /**< 異常位置 */ long m_errorId; /**< 異常編號 */ HiDBType m_dbTyp; /**< 數據庫類型 */ private: std::string m_errFull; };
實現:
#include "DB/HiDBCommon.h" #include <sstream> using namespace std; HiDBException::HiDBException() { m_errorId = 0; m_dbTyp = HiDBType_MySQL; } string HiDBException::ToSrting() { ostringstream oss; oss<<"Info:HiDBException" <<";DBType:"<<m_dbTyp <<";Position:"<<m_position <<";SQL:"<<m_sql <<";Description:"<<m_descript <<";Error ID:"<<m_errorId; return oss.str(); } const char* HiDBException::what() const { HiDBException* ex = const_cast<HiDBException*>(this); ex->m_errFull = ex->ToSrting(); return m_errFull.c_str(); }
為了方便的拋出異常,我提供了兩個宏(HiDBHelperOnError_void暫時只在處理事務時使用到):
/** @brief 異常語句宏 */ #define HiDBHelperOnError_void(ps, script,sql, id) \ HiDBException exception;\ exception.m_position = ps;\ exception.m_descript = script;\ exception.m_sql = sql;\ exception.m_errorId = id;\ logFun(HiDLT_Exception, exception.what());\ throw exception; /** @brief 異常語句宏 */ #define HiDBHelperOnError(ps, script,sql, id) \ HiDBException exception;\ exception.m_position = ps;\ exception.m_descript = script;\ exception.m_sql = sql;\ exception.m_errorId = id;\ logFun(HiDLT_Exception, exception.what());\ throw exception; //return false;
提供該宏,除了簡化用戶輸入外,還有一個想法就是如果用戶不想使用異常,可以修改該宏,例如返回false。
熟悉ADO.NET的朋友應該可以看出這些接口是借鑒了ADO.NET的。
(二)具體接口
<1> 構造函數
本來在《C++設計新思維》中,有個Policy classes,適合這種根據用戶需要提供安全或非安全接口的需求,但是Policy classes適合模板,不適合非模板,所以在這兒就沒有使用,只是在構造函數中添加了一個布爾參數isUsingLock,如果為true則提供線程安全接口,否則接口是非線程安全的。
HiDB打算在將來除支持MySQL外,還支持其他數據庫,所以在構造函數中,除isUsingLock外,還有一個選擇數據庫類型的接口。
將數據庫類型寫成枚舉,則枚舉為:
/** @brief 數據庫類型 */ enum HiDBType { HiDBType_Invail, /**< 無效類型 */ HiDBType_MySQL, /**< MySQL */ }; 構造函數的聲明就明確下來了: /** * @brief 構造函數 * @param[in] type 數據庫類型 * @param[in] isUsingLock 是否需要使用互斥鎖 */ HiDB(HiDBType type = HiDBType_MySQL, bool isUsingLock = false); <2>打開數據庫連接 打開數據庫連接比較簡單: /** * @brief 打開數據庫連接 * @param[in] conn 數據庫連接字符串 * @retval true:成功,false;失敗 * @par 實例: * @code * HiDB db; * if (db.Open("host=127.0.0.1;port=3306;dbname=test;user=root;pwd=root;charset=gbk;")) * { * // 打開成功 * } * else * { * // 打開失敗 * } * @endcode */ bool Open(const char* conn) throw (HiDBException);
該接口的conn參數是一個字符串,這樣字符串就具有擴展性,可以針對不同的數據庫,進行不同的處理(這一點感覺不是很好,但是能提供接口的穩定性)。不同數據庫,需要滿足特定的格式,在MySQL中,要使用類似於“host=127.0.0.1;port=3306;dbname=test;user=root;pwd=root;charset=gbk;”的格式。
<3> 關閉數據庫連接
/** * @brief 關閉據庫連接 */ void Close(void);
<4> IsOpen接口
這個接口是延伸出來的,既然有open,close,提供一個IsOpen好像也是應該的,讓用戶了解當前的打開狀態。
/** * @brief 數據庫連接是否打開 */ bool IsOpen();
<5> 執行非查詢語句接口
執行SQL語句的接口,應該可以接收可變參數,除可變參數外,還應該有一個包含sql語句的字符串參數,所以接口定義如下:
/** * @brief 執行SQL語句,並不返回結果 * @param[in] conn SQL語句 * @retval true:成功,false;失敗 * @par 實例: * @code * HiDB db; * if (db.ExecuteNoQuery("UPDATE table SET Paramer1='%s' * and Paramer2='%s' OR Paramer3=%d", "test1", "test2", 3)) * { * // 執行成功 * } * else * { * // 執行失敗 * } * @endcode */ bool ExecuteNoQuery(const char* sql, ...) throw (HiDBException);
<6> 獲得查詢結果第一行第一列的接口
該接口的參數與執行非查詢語句的接口一致,但是返回值應該為字符串,如果執行失敗,則應該返回空字符串。觸發異常時,拋出HiDBException異常。
/** * @brief 執行SQL語句,返回一個結果 * @param[in] sql SQL語句 * @retval 獲得的數據,如果為空,則失敗 */ std::string ExecuteScalar(const char* sql, ...) throw (HiDBException);
<7> 獲得一次查詢的所有記錄(多行多列)的接口
該接口的參數與執行非查詢語句的接口一致。
返回結果應該是包含多行多列的一個數據集,在ADO.NET中有DataTable,在這兒,我們可以用stl中的vector存儲多行,map存儲每行數據的多列。
多以需要定義一個數據集:
#ifndef HiDBTable typedef std::map<std::string, std::string> HiDBMap; /** @brief 查詢結果 */ typedef std::vector<HiDBMap> HiDBTable; /**< 查詢結果 */ #endif
因為HiDBTable中包含多個map,所以最好避免拷貝,使用stl的shared_ptr來避免多次拷貝:
/** * @brief 執行SQL語句,返回一個結果集合 * @param[in] sql SQL語句 * @retval 存儲查詢記錄的智能指針 */ std::shared_ptr<HiDBTable> ExecuteQuery(const char* sql, ...) throw (HiDBException);
<8> 執行事務的接口
執行事務接口是一個Command模式的接口,參數應該是一個函數對象。該對象為無參無返回值的函數對象即可。stl中提供了function對象。(在最初的版本中是自己實現函數對象的)
/** * @brief 在事務中執行處理 * @param[in] fun 處理函數 */ void OnTransaction(const std::function<void()>& fun) throw (HiDBException);
<9>日志回調接口
程序應該報出日志,在出現異常時需要報出異常日志;在調試時,最好能報出SQL語句。所以添加了日志回調接口:
日志回調函數:
/// @typedef std::function<void(HiDBLogType,const char*)> HiDBLogFun /// /// @brief 日志回調函數. typedef std::function<void(HiDBLogType,const char*)> HiDBLogFun;
日志類型:
/// @enum HiDBLogType /// /// @brief 日志類型. enum HiDBLogType { HiDLT_Normal, /// @brief 普通日志. HiDLT_Exception, /// @brief 異常日志. HiDLT_SQL /// @brief SQL語句日志,主要調試時使用. };
設置回調接口:
void SetLogFunction(const HiDBLogFun& fun);
(三) 接口的使用案例
HiDB m_DB = new HiDB(HiDBType_MySQL, true); try { bool ret = m_DB->Open( "host=127.0.0.1;port=3306;dbname=test;user=root;pwd=root;charset=gbk;" ); m_DB->ExecuteNoQuery("drop table if exists table1;"); string val = m_DB->ExecuteScalar( "SELECT column4 FROM table1 WHERE column1='%s' AND column3=%d", &val, "hitest", 59); shared_ptr<HiDBTable> table = this->m_DB->ExecuteQuery( "SELECT * FROM table1 WHERE column1='%s' OR column1='%s'", "hitest", "mytest"); } catch(HiDBException& e) { // ... }
(四) 其他
其實,我以前提供的接口比現在要復雜很多,首先我模仿ADO.NET,對SQL參數進行了封裝,封裝了SQL參數的名稱,類型,是否為空,值等信息,對獲得的數據也進行了類似的封裝。 ,還針對SQL參數提供了Create和Delete函數。
有一個同時看了我的接口后說,我的接口太復雜了,分層不是明確。我接受了他的建議,就將接口修改為現在的接口了。
另外,執行事務接口,最開始我是自己創建函數對象的,這也增加了復雜度,后來使用了stl的function對象,輔以lambda表達式,則使用起來簡單多了。
(五) 完整的接口:
<1>HiDBCommon.h 提供接口應用到的相關枚舉和結構體
#pragma once /** * @defgroup 數據庫模塊 * @{ */ #include "HiDBExport.h" #include <string> #include <vector> #include <map> #include <sstream> /** @brief 數據庫類型 */ enum HiDBType { HiDBType_Invail, /**< 無效類型 */ HiDBType_MySQL, /**< MySQL */ }; #ifndef HiDBTable typedef std::map<std::string, std::string> HiDBMap; /** @brief 查詢結果 */ typedef std::vector<HiDBMap> HiDBTable; /**< 查詢結果 */ #endif /** @brief 數據庫操作異常 */ class HI_DB_EXPORT HiDBException: public std::exception { public: HiDBException(); public: std::string ToSrting(); const char* what() const; public: std::string m_sql; /**< 本次操作的SQL語句 */ std::string m_descript; /**< 異常描述 */ std::string m_position; /**< 異常位置 */ long m_errorId; /**< 異常編號 */ HiDBType m_dbTyp; /**< 數據庫類型 */ private: std::string m_errFull; }; /** @brief 異常語句宏 */ #define HiDBHelperOnError_void(ps, script,sql, id) \ HiDBException exception;\ exception.m_position = ps;\ exception.m_descript = script;\ exception.m_sql = sql;\ exception.m_errorId = id;\ logFun(HiDLT_Exception, exception.what());\ throw exception; /** @brief 異常語句宏 */ #define HiDBHelperOnError(ps, script,sql, id) \ HiDBException exception;\ exception.m_position = ps;\ exception.m_descript = script;\ exception.m_sql = sql;\ exception.m_errorId = id;\ logFun(HiDLT_Exception, exception.what());\ throw exception; //return false; /**//** @}*/ // 數據庫模塊
<2> HiDB.h 主要的接口:
#pragma once /** * @defgroup 數據庫模塊 * @{ */ #include <memory> #include <functional> #include "HiDBCommon.h" class HiDBImpl; #pragma warning (disable: 4290) /// @enum HiDBLogType /// /// @brief 日志類型. enum HiDBLogType { HiDLT_Normal, /// @brief 普通日志. HiDLT_Exception, /// @brief 異常日志. HiDLT_SQL /// @brief SQL語句日志,主要調試時使用. }; /// @typedef std::function<void(HiDBLogType,const char*)> HiDBLogFun /// /// @brief 日志回調函數. typedef std::function<void(HiDBLogType,const char*)> HiDBLogFun; /** * @brief 數據庫操作類,封裝數據庫的通用操作,本類使用策略模式實現 * @author 徐敏榮 * @date 2012-06-14 * * @par 修訂歷史 * @version v0.5 \n * @author 徐敏榮 * @date 2012-06-14 * @li 初始版本 * @version v0.6 \n * @author 徐敏榮 * @date 2014-08-04 * @li 簡化程序 * @date 2014-08-04 * @li 添加錯誤信息的報出,擴展異常為繼承自std::exception * @date 2014-09-11 * @li 修復多次close時崩潰的問題 * */ class HI_DB_EXPORT HiDB { public: /** * @brief 構造函數 * @param[in] type 數據庫類型 * @param[in] isUsingLock 是否需要使用互斥鎖 */ HiDB(HiDBType type = HiDBType_MySQL, bool isUsingLock = false); /** * @brief 析構函數 */ ~HiDB(); public: /** * @brief 打開數據庫連接 * @param[in] conn 數據庫連接字符串 * @retval true:成功,false;失敗 * @par 實例: * @code * HiDB db; * if (db.Open("host=127.0.0.1;port=3306;dbname=test;user=root;pwd=root;charset=gbk;")) * { * // 打開成功 * } * else * { * // 打開失敗 * } * @endcode */ bool Open(const char* conn) throw (HiDBException); /** * @brief 關閉據庫連接 */ void Close(void); /** * @brief 數據庫連接是否打開 */ bool IsOpen(); public: /** * @brief 執行SQL語句,並不返回結果 * @param[in] conn SQL語句 * @retval true:成功,false;失敗 * @par 實例: * @code * HiDB db; * if (db.ExecuteNoQuery("UPDATE table SET Paramer1='%s' * and Paramer2='%s' OR Paramer3=%d", "test1", "test2", 3)) * { * // 執行成功 * } * else * { * // 執行失敗 * } * @endcode */ bool ExecuteNoQuery(const char* sql, ...) throw (HiDBException); public: /** * @brief 執行SQL語句,返回一個結果 * @param[in] sql SQL語句 * @retval 獲得的數據,如果為空,則失敗 */ std::string ExecuteScalar(const char* sql, ...) throw (HiDBException); public: /** * @brief 執行SQL語句,返回一個結果集合 * @param[in] sql SQL語句 * @retval 存儲查詢記錄的智能指針 */ std::shared_ptr<HiDBTable> ExecuteQuery(const char* sql, ...) throw (HiDBException); public: /** * @brief 在事務中執行處理 * @param[in] fun 處理函數 */ void OnTransaction(const std::function<void()>& fun) throw (HiDBException); public: void SetLogFunction(const HiDBLogFun& fun); private: /** * @brief 數據庫操作實現指針 */ HiDBImpl* m_Impl; /**< 數據庫操作實現指針 */ HiDBLogFun logFun; }; /**//** @}*/ // 數據庫模塊
三 實現
實現采用了從《Effective C++》中學習到的實現與接口相分析的原則,在HiDB中使用HiDBImpl實現訪問數據庫的邏輯。
(一) 可變參數的處理
當然,在HiDB中是需要解決根據sql參數和可變參數拼裝成一個完整SQL語句的問題。
該問題使用一個宏來實現:
#if !defined(HISDB_ON_VARLIST) #define HISDB_ON_VARLIST(x, y) \ char chArr[2048] = {0};\ char* pchar = &chArr[0];\ va_list pArgList;\ va_start(pArgList, y);\ ::_vsnprintf(chArr, 2047, x, pArgList); \ va_end(pArgList) ;\ logFun(HiDLT_SQL, chArr); #endif
(二) 互斥鎖的實現
自己根據臨界區,實現了一個互斥鎖,互斥鎖接口如下:
1: 構造函數: 實現臨界區的初始化
2: 析構函數: 實現臨界區的刪除
3: 進入臨界區
4: 離開臨界區
實現函數如下:
/** * @brief 臨界區訪問類,主要封裝windows臨界區的訪問,該類主要在棧中使用,利用局部變量的構造和析構函數出入臨界區 * @author 徐敏榮 * @date 2012-06-14 * * @par 修訂歷史 * @version v0.5 \n * @author 徐敏榮 * @date 2012-06-14 * @li 初始版本 * */ class HiCritical { public: /** * @brief 構造函數 */ HiCritical() { ::InitializeCriticalSection(&cs); } /** * @brief 析構函數 */ ~HiCritical() { ::DeleteCriticalSection(&cs); } /** * @brief 進入臨界區 */ void Enter() { ::EnterCriticalSection(&cs); } /** * @brief 離開臨界區 */ void Leave() { ::LeaveCriticalSection(&cs); } CRITICAL_SECTION* GetSection() { return &cs; } private: /** * @brief 臨界區對象 */ CRITICAL_SECTION cs; /**< 臨界區對象 */ };
另外還提供一個臨界區管理類(HiCriticalMng),在構造該類時,進入臨界區,析構該類時離開臨界區。如果構造函數中傳入的是NULL,則不進行任何互斥處理。
/** * @brief 臨界區訪問管理類,利用構造函數進入臨界區,利用西溝函數離開臨界區 * 如果向構造函數提供NULL參數,則不使用臨界區。 * */ class HiCriticalMng { public: HiCriticalMng(HiCritical& crl): cl(&crl) { cl->Enter(); } HiCriticalMng(HiCritical* crl): cl(crl) { if (cl) { cl->Enter(); } } ~HiCriticalMng() { if (cl) { cl->Leave(); } } private: HiCritical* cl; };
(三) HiDBImpl的接口
作為數據庫訪問類,HiDBImpl實現HiDB需要的所有接口,所以HiDBImpl與HiDB接口類似,但是HiDBImpl接口接收的參數為完整的SQL語句(因為帶可變參數的SQL語句已經被HiDB處理了)。
HiDBImpl不但要支持MySQL,以后還要支持其他數據庫,所以不能有LibMySQL相關的東西,HiDBImpl應該是一個基類,可以被派生,例如派生出支持LibMySQL的子類。
HiDBImpl要線程安全的,所以要包含互斥鎖HiCritical,又要可以非線程安全(HiCriticalMng支持NULL參數),所以HiCritical需要時這指針,這樣,HiDBImpl的接口就出來了。
HiDBImpl接口如下:
#pragma once /** * @defgroup 數據庫操作實現類接口類 * @brief 數據庫操作實現類接口類,聲明數據庫操作實現類的接口。 * @author 徐敏榮 * @date 2012-06-14 * * @par 修訂歷史 * @version v0.5 \n * @author 徐敏榮 * @date 2012-06-14 * @li 初始版本 * @{ */ #include "DB/HiDB.h" class HiCritical; /** * @brief 數據庫操作實現類接口類,聲明數據庫操作實現類的接口 * */ class HiDBImpl { public: /** * @brief 構造函數 * @param[in] isUsingLock 是否需要使用互斥鎖 */ HiDBImpl(bool isUsingLock); /** * @brief 析構函數 */ virtual ~HiDBImpl(); public: /** * @brief 打開數據庫連接 * @param[in] conn 數據庫連接字符串 * @retval true:成功,false;失敗 */ virtual bool Open(const char* conn) = 0; /** * @brief 關閉據庫連接 */ virtual void Close(void) = 0; public: /** * @brief 執行SQL語句,並不返回結果 * @param[in] conn SQL語句 * @retval true:成功,false;失敗 */ virtual bool ExecuteNoQuery(const char* sql) = 0; public: /** * @brief 執行SQL語句,返回一個結果 * @param[in] sql SQL語句 * @param[out] value 取得的結果 * @retval true:成功,false;失敗 */ virtual std::string ExecuteScalar(const char* sql) = 0; public: /** * @brief 執行SQL語句,返回一個結果集合 * @param[in] sql SQL語句 * @param[out] table 取得的結果集合 * @retval true:成功,false;失敗 */ virtual std::shared_ptr<HiDBTable> ExecuteQuery(const char* sql) = 0; public: /** * @brief 事物處理 * @retval true:成功,false;失敗 */ virtual void OnTransaction(const std::function<void()>& fun) = 0; public: /** * @brief 釋放查詢集合資源 * @param[in] table 查詢的集合資源 * @retval true:成功,false;失敗 */ bool ReleaseHiDBTable(HiDBTable** table); public: void SetLogFunction(const HiDBLogFun& fun); protected: /** * @brief 臨界區對象,為空表示不需要考慮資源並發訪問 */ HiCritical* m_pCritical; public: HiDBLogFun logFun; }; /**//** @}*/ // 數據庫操作實現類接口類
HiDBImpl.cpp:
#include "HiDBImpl.h" #include "Common/HiCritical.h" static void defaultFun(HiDBLogType type,const char* ex) { } HiDBImpl::HiDBImpl(bool isUsingLock) { this->m_pCritical = NULL; if (isUsingLock) { this->m_pCritical = new HiCritical(); } logFun = defaultFun; } HiDBImpl::~HiDBImpl() { if (this->m_pCritical) { delete this->m_pCritical; this->m_pCritical = NULL; } } void HiDBImpl::SetLogFunction(const HiDBLogFun& fun) { logFun = fun; }
默認日志回調函數什么都不做,使用了一個static的函數。
(三) HiDBImpl的派生類HiDBMySQL
HiDBMySQL是HiDBImpl的派生類,主要用於實現MySQL相關功能,使用LibMySQL實現。
HiDBMySQL.h
#pragma once /** * @defgroup 數據庫操作實現類接口類 * @brief 數據庫操作實現類接口類,聲明數據庫操作實現類的接口。 * @author 徐敏榮 * @date 2012-06-14 * * @par 修訂歷史 * @version v0.5 \n * @author 徐敏榮 * @date 2012-06-14 * @li 初始版本 * @{ */ #include<winsock2.h> #include <functional> #include "mysql/mysql.h" #include "HiDBImpl.h" /** * @brief 數據庫操作實現類接口類,聲明數據庫操作實現類的接口 * */ class HiDBMySQL: public HiDBImpl { public: /** * @brief 構造函數 * @param[in] isUsingLock 是否需要使用互斥鎖 */ HiDBMySQL(bool isUsingLock); /** * @brief 析構函數 */ ~HiDBMySQL(); public: /** * @brief 打開數據庫連接 * @param[in] conn 數據庫連接字符串 * @retval true:成功,false;失敗 */ bool Open(const char* conn); /** * @brief 關閉據庫連接 */ void Close(void); public: /** * @brief 執行SQL語句,並不返回結果 * @param[in] conn SQL語句 * @retval true:成功,false;失敗 */ bool ExecuteNoQuery(const char* sql); public: /** * @brief 執行SQL語句,返回一個結果 * @param[in] sql SQL語句 * @param[out] value 取得的結果 * @retval true:成功,false;失敗 */ std::string ExecuteScalar(const char* sql); public: /** * @brief 執行SQL語句,返回一個結果集合 * @param[in] sql SQL語句 * @param[out] table 取得的結果集合 * @retval true:成功,false;失敗 */ std::shared_ptr<HiDBTable> ExecuteQuery(const char* sql); public: /** * @brief 事物處理 * @param[in] command 用戶自定義命令 * @retval true:成功,false;失敗 */ void OnTransaction(const std::function<void()>& fun); private: bool IsOpen(void); private: MYSQL* m_pSQLData; MYSQL m_SQLData; std::string m_ConnString; }; /**//** @}*/ // 數據庫操作實現類接口類
HiDBMySQL.cpp
#include "HiDBMySQL.h" #include "common/HiCritical.h" using namespace std; // 構造函數 HiDBMySQL::HiDBMySQL(bool isUsingLock): HiDBImpl(isUsingLock), m_pSQLData(NULL) { } // 析構函數 HiDBMySQL::~HiDBMySQL() { if(this->m_pSQLData) { ::mysql_close(this->m_pSQLData); this->m_pSQLData = NULL; } } // 打開數據庫連接 bool HiDBMySQL::Open(const char* conn) { HiCriticalMng msg(this->m_pCritical); if(this->m_pSQLData) { return true; } if (::strlen(conn) == 0) { return false; } char host[40] = {0}; char dbname[40] = {0}; long port = 0; char user[40] = {0}; char pwd[40] = {0}; char chrSet[40] = {0}; ::sscanf(conn, ("host=%[^;];port=%d;dbname=%[^;];user=%[^;];pwd=%[^;];charset=%[^;];"), host, &port, dbname,user, pwd, chrSet); ::mysql_init(&this->m_SQLData); if(::mysql_real_connect(&this->m_SQLData, host, user, pwd, dbname, port, NULL, 0)) { this->m_pSQLData = &this->m_SQLData; ostringstream oss; oss<<"set names "<<chrSet; ::mysql_query(this->m_pSQLData, oss.str().c_str()); this->m_ConnString = conn; return true; } else { if(this->m_pSQLData) { long id = mysql_errno(this->m_pSQLData); const char* err = mysql_error(this->m_pSQLData); //關閉連接 ::mysql_close(this->m_pSQLData); this->m_pSQLData = NULL; HiDBHelperOnError("Open(const char*)", err, "", id); } return false; } } void HiDBMySQL::Close(void) { HiCriticalMng msg(this->m_pCritical); if(this->m_pSQLData) { ::mysql_close(this->m_pSQLData); this->m_pSQLData = NULL; } } bool HiDBMySQL::ExecuteNoQuery(const char* sql) { HiCriticalMng msg(this->m_pCritical); if (!this->IsOpen()) { //HiDBHelperOnError("ExecuteNoQuery(const char*)", "Database is not open", sql, 0); return false; } long error = ::mysql_query(this->m_pSQLData, sql); bool result = (error == 0)? true:false; if (result) { MYSQL_RES *pResult = ::mysql_store_result(this->m_pSQLData); if(pResult) { ::mysql_free_result(pResult); //釋放數據集 } return true; } HiDBHelperOnError("ExecuteNoQuery(const char*)", mysql_error(this->m_pSQLData), sql, mysql_errno(this->m_pSQLData)); } string HiDBMySQL::ExecuteScalar(const char* sql) { HiCriticalMng msg(this->m_pCritical); if (!this->IsOpen()) { //HiDBHelperOnError("ExecuteNoQuery(const char*)", "Database is not open", sql, 0); return ""; } long error = ::mysql_query(this->m_pSQLData, sql); if(error != 0)//執行SQL語句 { HiDBHelperOnError("HiDBMySQL::ExecuteScalar(const char*, HiDBValue&)", ::mysql_error(this->m_pSQLData), sql, error); } MYSQL_RES* pResult= ::mysql_store_result(this->m_pSQLData); //獲取數據集 if(!pResult) { HiDBHelperOnError("HiDBMySQL::ExecuteScalar(const char*, HiDBValue&)", ::mysql_error(this->m_pSQLData), sql, mysql_errno(this->m_pSQLData)); } if (pResult->row_count == 0) { ::mysql_free_result(pResult); //釋放數據集 return ""; } MYSQL_FIELD* pFields = ::mysql_fetch_field(pResult); ::mysql_data_seek(pResult, 0); MYSQL_ROW curRow = ::mysql_fetch_row(pResult); string ret = curRow[0]; ::mysql_free_result(pResult); //釋放數據集 return ret; } std::shared_ptr<HiDBTable> HiDBMySQL::ExecuteQuery(const char* sql) { HiCriticalMng msg(this->m_pCritical); if (!this->IsOpen()) { //HiDBHelperOnError("ExecuteNoQuery(const char*)", "Database is not open", sql, 0); return NULL; } long error = ::mysql_query(this->m_pSQLData, sql); if(error != 0)//執行SQL語句 { HiDBHelperOnError("HiDBMySQL::ExecuteQuery(const char*)", ::mysql_error(this->m_pSQLData), sql, error); } MYSQL_RES* pResult= ::mysql_store_result(this->m_pSQLData); //獲取數據集 if(!pResult) { HiDBHelperOnError("HiDBMySQL::ExecuteQuery(const char*)", ::mysql_error(this->m_pSQLData), sql, mysql_errno(this->m_pSQLData)); } std::shared_ptr<HiDBTable> tb(new HiDBTable()); if (pResult->row_count == 0) { ::mysql_free_result(pResult); //釋放數據集 return tb; } MYSQL_FIELD* pFields = ::mysql_fetch_field(pResult); for (int i = 0; i < pResult->row_count; i++) { ::mysql_data_seek(pResult, i); MYSQL_ROW curRow = ::mysql_fetch_row(pResult); map<string, string> list; tb->push_back(list); map<string, string>& list2 = *tb->rbegin(); for (int j = 0; j < (long)pResult->field_count; j++) { string val = ""; if (curRow[j]) { val = curRow[j]; } list2[pFields[j].name] = val; } } ::mysql_free_result(pResult); //釋放數據集 return tb; } void HiDBMySQL::OnTransaction(const std::function<void()>& fun) { HiCriticalMng msg(this->m_pCritical); mysql_autocommit(this->m_pSQLData, 0); try { fun(); if (::mysql_commit(this->m_pSQLData) == 0) { mysql_autocommit(this->m_pSQLData, 1); return; } if (::mysql_rollback(this->m_pSQLData) == 0) { mysql_autocommit(this->m_pSQLData, 1); } HiDBHelperOnError_void("HiDBMySQL::OnTransaction", "execute transaction sucess,but commit failed", "", 0); } catch(HiDBException& e) { if (::mysql_rollback(this->m_pSQLData) == 0) { mysql_autocommit(this->m_pSQLData, 1); } HiDBHelperOnError_void("HiDBMySQL::RollbackTransaction", e.m_descript, e.m_sql, e.m_errorId); } catch (...) { if (::mysql_rollback(this->m_pSQLData) == 0) { mysql_autocommit(this->m_pSQLData, 1); } HiDBHelperOnError_void("HiDBMySQL::RollbackTransaction", ::mysql_error(this->m_pSQLData), "", mysql_errno(this->m_pSQLData)); } } bool HiDBMySQL::IsOpen(void) { return this->m_pSQLData == NULL ? false : true; }
(四)HiDB的實現:
由HiDB負責實現可變參數轉換為完整SQL語句,HiDBImpl負責實現所有數據庫訪問邏輯,並要為以后添加其他數據庫支持這些需求可以推到出HiDB的實現代碼:
#include <stdarg.h> #include "DB/HiDB.h" #include "HiDBMySQL.h" using namespace std; #if !defined(HISDB_ON_VARLIST) #define HISDB_ON_VARLIST(x, y) \ char chArr[2048] = {0};\ char* pchar = &chArr[0];\ va_list pArgList;\ va_start(pArgList, y);\ ::_vsnprintf(chArr, 2047, x, pArgList); \ va_end(pArgList) ;\ logFun(HiDLT_SQL, chArr); #endif static void defaultFun(HiDBLogType type,const char* ex) { } static bool IsImplOK(HiDBImpl* db) { if (!db) { return false; } /* if (!db->IsOpen()) { return false; }*/ return true; } // 構造函數 HiDB::HiDB(HiDBType type, bool isUsingLock):m_Impl(NULL) { if (type == HiDBType_MySQL) { this->m_Impl = new HiDBMySQL(isUsingLock); } logFun = defaultFun; } // 析構函數 HiDB::~HiDB() { if (this->m_Impl) { delete this->m_Impl; this->m_Impl = NULL; } } // 打開數據庫連接 bool HiDB::Open(const char* conn) { if (!this->m_Impl) { return false; } return this->m_Impl->Open(conn); } bool HiDB::IsOpen() { if (!this->m_Impl) { return false; } return true;//this->m_Impl->IsOpen(); } void HiDB::Close(void) { if (!IsImplOK(this->m_Impl)) { return; } return this->m_Impl->Close(); } bool HiDB::ExecuteNoQuery(const char* sql, ...) { if (!IsImplOK(this->m_Impl)) { return false; } HISDB_ON_VARLIST(sql, sql); return this->m_Impl->ExecuteNoQuery(chArr); } string HiDB::ExecuteScalar(const char* sql, ...) { if (!IsImplOK(this->m_Impl)) { return ""; } HISDB_ON_VARLIST(sql, sql); return this->m_Impl->ExecuteScalar(chArr); } std::shared_ptr<HiDBTable> HiDB::ExecuteQuery(const char* sql, ...) { if (!IsImplOK(this->m_Impl)) { return NULL; } HISDB_ON_VARLIST(sql, sql); return this->m_Impl->ExecuteQuery(chArr); } void HiDB::OnTransaction(const std::function<void()>& fun) { if (!IsImplOK(this->m_Impl)) { HiDBHelperOnError_void("HiDB::OnTransaction", "HiDB is not impl", "", 0); } return this->m_Impl->OnTransaction(fun); } void HiDB::SetLogFunction(const HiDBLogFun& fun) { logFun = fun; if (this->m_Impl) { this->m_Impl->SetLogFunction(fun); } }
四 后記
至此,HiDB所有主要的接口和主要的實現就介紹的差不多,其他更多的實現,可以參照源碼自己實現。類圖將在本文后面提供。
后續的工作包括對HiDB進行測試,我期望能進行自動化測試,類似於Junit。但是我並不知道C++有哪些自動化測試工具,沒辦法,只有自己寫C++自動化測試工具來測試HiDB了,后面的文章我打算介紹一下我寫的一個C++自動化測試工具。
HiDB類圖:

源代碼:http://download.csdn.net/detail/xumingxsh/7778417
我用“逆水行船”賬號發布不到首頁,就用這個賬號發布吧。
這個程序自己陸陸續續編寫修改了好幾年,我就不信沒人看,沒人覺得有用。