数据库接口学习和研究(1)

来源:互联网 发布:尼尔森数据分析培训 编辑:程序博客网 时间:2024/04/29 06:59

     由于工作关系,我在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年前的网络游戏《红月》的数据库,我就是连接这个把里面的人物数据打印出来。怎么样,这个版本够简单吧?还有部分函数没实现呢。明天再弄吧。