一、概述
在我参与的多个项目中,大家使用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
我用“逆水行船”账号发布不到首页,就用这个账号发布吧。
这个程序自己陆陆续续编写修改了好几年,我就不信没人看,没人觉得有用。