學習實踐:使用模式,原則實現一個C++資料庫訪問類

來源:互聯網
上載者:User

標籤:des   blog   http   java   使用   os   io   資料   

一、概述在我參與的多重專案中,大家使用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:HiDBException();public:std::string ToSrting();public:std::string m_sql; /**< 本次操作的SQL語句 */std::string m_descript; /**< 異常描述 */std::string m_position; /**< 異常位置 */long m_errorId; /**< 異常編號 */HiDBType m_dbTyp; /**< 資料庫類型 */};

 

  為了方便的拋出異常,我提供了一個宏(這個宏只有HiDB庫使用)
/** @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;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> 獲得查詢結果第一行第一列的介面該介面的參數與執行非查詢語句的介面一致,但是傳回值應該為字串,如果執行失敗,則應該返回Null 字元串。觸發異常時,拋出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);

 

 (三) 介面的使用案例
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:HiDBException();public:std::string ToSrting();public:std::string m_sql; /**< 本次操作的SQL語句 */std::string m_descript; /**< 異常描述 */std::string m_position; /**< 異常位置 */long m_errorId; /**< 異常編號 */HiDBType m_dbTyp; /**< 資料庫類型 */}; /** @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;throw exception;//return false; /**//** @}*/ // 資料庫模組

 

 <2> HiDB.h 主要的介面:
#pragma once /*** @defgroup 資料庫模組* @{*/ #include <memory>#include <functional> #include "HiDBCommon.h" class HiDBImpl; #pragma warning (disable: 4290) /*** @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 簡化程式**/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); private:/*** @brief 資料庫操作實現指標*/HiDBImpl* m_Impl; /**< 資料庫操作實現指標 */}; /**//** @}*/ // 資料庫模組

 

 三 實現實現採用了從《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) ;#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;  protected: /*** @brief 臨界區對象,為空白表示不需要考慮資源並發訪問*/HiCritical* m_pCritical;}; /**//** @}*/ // 資料庫操作實作類別介面類

 

 (四)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) ;#endif  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);}} // 解構函式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("HiDB::OnTransaction","HiDB is not impl", "", 0);}return this->m_Impl->OnTransaction(fun);}

 

 四 後記至此,HiDB所有主要的介面和主要的實現就介紹的差不多,其他更多的實現,可以參照源碼自己實現。類圖將在本文後面提供。後續的工作包括對HiDB進行測試,我期望能進行自動化測試,類似於Junit。但是我並不知道C++有哪些自動化測試載入器,沒辦法,只有自己寫C++自動化測試載入器來測試HiDB了,後面的文章我打算介紹一下我寫的一個C++自動化測試載入器。HiDB類圖:  原始碼:http://download.csdn.net/detail/xumingxsh/7778417 我用“逆水行船”帳號發布不到首頁,就用這個帳號發布吧。這個程式自己陸陸續續編寫修改了好幾年,我就不信沒人看,沒人覺得有用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.