zoukankan      html  css  js  c++  java
  • 学习实践:使用模式,原则实现一个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 
    
    *     @author  徐敏荣
    *    @date    2012-06-14
    *    @li 初始版本
    *    @version v0.6 
    
    *     @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 
    
    * @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 
    
    *     @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 
    
    *     @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
     
    我用“逆水行船”账号发布不到首页,就用这个账号发布吧。
    这个程序自己陆陆续续编写修改了好几年,我就不信没人看,没人觉得有用。
  • 相关阅读:
    mysql 修改时锁定技术
    eclipse配置java虚拟机的方法 转
    Highcharts2.3.2 网页曲线绘制工具 一淘网价格曲线
    Linux Shell常用技巧(目录) by Stephen Liu
    为zend studio添加phpdocumentor插件
    graphviz入门
    性价比超高的北斗小辣椒
    notepad++和graphviz配合使用
    搜狗的三级火箭
    电信版小黄蜂 双模天语E619亮相3G展会
  • 原文地址:https://www.cnblogs.com/Rong-/p/3928592.html
Copyright © 2011-2022 走看看