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


一、概述
在我參與的多個項目中,大家使用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
 
我用“逆水行船”賬號發布不到首頁,就用這個賬號發布吧。
這個程序自己陸陸續續編寫修改了好幾年,我就不信沒人看,沒人覺得有用。


免責聲明!

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



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