由于工作关系,我在windows和solaris系统下,分别使用过windows SQL server 2000、sybase 12/12.5、sqlite。
今年oracle要收购sun让我也开始关心MySql,我还没用过呢,难道MySql就此要和开源作别了?
为此我开始有些兴趣重新学习和研究一下数据库。之前我用于编程的一般使用SourcePro或直接用sqlite提供的API。我开始找一些书,一点一滴的学习一下。
一、ODBC。不用说,Microsoft的产品,开源人员一般都不喜欢微软的东西,虽然我工作于商业公司,但是我私下也是一个开源参与者。ODBC,开放式数据库连接性,根据我查询MSDN的内容,我写了一个非常简单的ODBC封装类,当然我还会继续不断完善。MSDN上也提供了一个样例,但是我觉得那个样例,实在写得不好。
我考虑了一会,还是由浅入深。数据库编程操作一般无非就是连接,打开,执行SQL语句,如果是查询会有一些返回值、排序等,然后是释放缓存,关闭连接。OK,就做这么简单的吧,有需要再增加吧。大部分时候我还是对C语言兴趣更浓,也更喜欢一些,因此我一般采用C语言风格编写代码。这个是头文件:odbc_dbmgr.h:
#ifndef __ODBC_DBMGR_H__
#define __ODBC_DBMGR_H__
#include <Windows.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
class ODBC_DBMGR{
public:
ODBC_DBMGR();
virtual ~ODBC_DBMGR();
public: // 用法:使用Open打开一个DSN,使用ExecSql执行SQL语句,使用Close关闭打开的DSN
int ODBC_Open(const char* szDSN, const char* szUser, const char* szPassword);
void ODBC_Close(void);
int ODBC_ExecSql(char* szSql);
// 执行查询之后下面函数起作用
short ODBC_GetResultCols(void);
int ODBC_GetResultRows(void);
int ODBC_BindCol2Char(short nCol, char* szColValue, int nColValueLen); // 绑定列为C_char类型
int ODBC_BindCol2Int(short nCol, int &iColValue); // 绑定列为int型
// int ODBC_BindCol2Bit(short nCol);
private:
SQLSMALLINT ODBC_Init(void);
private:
SQLHENV m_henv; // ODBC 环境
SQLHDBC m_hdbc; // ODBC 句柄
SQLHSTMT m_hstmt; // 返回值句柄
bool m_bIsConnected; // 是否连接的标志
};
#endif
我最初的设想是:构造ODBC_DBMGR就传入DSN+用户名+密码。后来考虑了一下,如果后续我要重构成singleton模式,这样就做不到了,因此还是把Open操作单独提炼出来。
微软的SDK往往都有一个特点:第一步必须要初始化SDK环境。这样的例子很多,象WINDOWS SOCKET1/2、象OLE、象ODBC。因此,每次用微软的SDK,一般都会先使用一个init函数。ODBC里面有2.0/3.0驱动的版本很多,各个版本都存在一定的不兼容问题,所以才需要初始化驱动环境。最初我很不理解,为什么一个公司的一个软件不同升级版本还不能互相兼容?直到我自己身处一个巨型的软件公司,里面的大平台、小软件各个版本升级后都无法兼容,巨大的组织结构、复杂的部门关系、剧烈的组织变革、脆弱的维护团队,短暂的开发周期,所有的这一切造成了3.0/2.0的不兼容或者功能丢失。微软的SDK有时也很有趣,MSDN会告诉你:2.0版本xxx函数在3.0版本已经不存在,被xxxEx函数所取代。
Open+Close或者malloc+free或者new+delete,这些词就像孪生兄弟一样,无论走到哪都会成对出现,很多书、人都会教育大家,要成对声明、成对编程。这些是C/C++初生阶段的标志性产物。ODBC作为一个C语言为王时代的产品,自然也少不了。
然后根据需要,定义了几个私有变量。从个人习惯而言,我不太喜欢protect,性格使然。这几个变量中,最重要的当属m_hdbc。
下面是我的odbc_dbmgr.cpp:
#include <stdio.h>
#include "odbc_dbmgr.h"
// 遇到错误返回的宏
#define FAILPAUSE(ret) /
if (SQL_SUCCESS != ret) { /
printf("SQL Errors:%d/n", ret); /
ODBC_Close(); /
return ret; }
ODBC_DBMGR::ODBC_DBMGR()
{
m_hdbc = NULL;
m_henv = NULL;
m_hstmt = NULL;
m_bIsConnected = false;
}
ODBC_DBMGR::~ODBC_DBMGR()
{
ODBC_Close();
}
// 初始化,完成各种环境设置
SQLSMALLINT ODBC_DBMGR::ODBC_Init()
{
SQLRETURN ret;
ret = ::SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv);
FAILPAUSE(ret);
ret = ::SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
FAILPAUSE(ret);
ret = ::SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &m_hdbc);
FAILPAUSE(ret);
return SQL_SUCCESS;
}
//
// 打开一个DSN,打开之前首先初始化
int ODBC_DBMGR::ODBC_Open(const char* szDSN, const char* szUser, const char* szPassword)
{
SQLRETURN ret;
ret = ODBC_Init();
FAILPAUSE(ret);
ret = ::SQLConnect(m_hdbc,
(SQLCHAR*)szDSN, SQL_NTS,
(SQLCHAR*)szUser, SQL_NTS,
(SQLCHAR*)szPassword, SQL_NTS);
if (SQL_SUCCESS == ret || SQL_SUCCESS_WITH_INFO == ret)
{
m_bIsConnected = true;
ret = ::SQLAllocHandle(SQL_HANDLE_STMT, SQL_NULL_HANDLE, &m_hstmt);
FAILPAUSE(ret);
}
return 0;
}
//
// 执行SQL语句
int ODBC_DBMGR::ODBC_ExecSql(char* szSql)
{
SQLRETURN ret;
ret = ::SQLPrepare(m_hstmt, (SQLCHAR*)szSql, SQL_NTS);
FAILPAUSE(ret);
ret = ::SQLExecute(m_hstmt);
FAILPAUSE(ret);
return 0;
}
//
//返回结果集列数
short ODBC_DBMGR::ODBC_GetResultCols()
{
short nCols = 0;
::SQLNumResultCols(m_hstmt, &nCols);
return nCols;
}
//
// 绑定某列为字符串
int ODBC_DBMGR::ODBC_BindCol2Char(short nCol, char* szColValue, int nColValueLen)
{
SQLLEN cb;
SQLRETURN ret;
ret = ::SQLBindCol(m_hstmt, nCol, SQL_C_CHAR, szColValue, nColValueLen, &cb);
FAILPAUSE(ret);
ret = ::SQLFetch(m_hstmt);
FAILPAUSE(ret);
return 0;
}
// 关闭连接
void ODBC_DBMGR::ODBC_Close()
{
if (m_henv){
if (m_hdbc){
if (m_bIsConnected){
if (m_hstmt){
::SQLFreeHandle(SQL_HANDLE_STMT, m_hstmt);
}
::SQLDisconnect(m_hdbc);
m_bIsConnected = false;
}
::SQLFreeHandle(SQL_HANDLE_DBC, m_hdbc);
m_hdbc = NULL;
}
::SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
m_henv = NULL;
}
}
FAILPAUSE宏是我比较喜欢用的一种手法,一般情况下我会打印一句话,然后sleep3~5s。很多教科书都教唆大家,在C++时代,要使用inline函数,不要使用宏定义。由于习惯,我还是比较喜欢宏。象这种遇到错误返回的宏、LOG_ERROR/LOG_INFO(记日志用)、或是定义一个singleton、LOADDLL等等。另外,在做大型软件时,很多时候做完了要不断做性能优化,往往到后面,大家都会把inline变成marco,把std::string定义的全局字符串用#define替换掉。因为,这些都有时间或空间的开销。
上面的代码都非常简单,也没有过多描述的必要。
最后要写一个main函数来试着玩玩这个接口。我只写了一个架子,等我下一个版本优化一下再补全main.cpp:
#include <stdio.h>
#include "odbc_dbmgr.h"
// main
int main(int argc, char* argv[])
{
ODBC_DBMGR db;
int col = 0;
int i = 0;
db.ODBC_Open("RMEBrain0", "sa", "");
db.ODBC_ExecSql((char*)"SELECT * FROM tblGameID1");
col = db.ODBC_GetResultCols();
for (i = 0; i < col; i++)
{
}
getchar();
db.ODBC_Close();
}
^_^我本机有9年前的网络游戏《红月》的数据库,我就是连接这个把里面的人物数据打印出来。怎么样,这个版本够简单吧?还有部分函数没实现呢。明天再弄吧。