概述
在笔者所开发过的产品中,有很多都需要与Oracle数据库打交道。为了实现C代码与Oracle数据库的消息交互,Oracle公司为广大的开发者们提供了一个统一的调用接口OCI(Oracle Call Interface)。只要按照规范来调用OCI中的函数,就能够实现C代码与Oracle数据库的交互。
具体而言,OCI的C语言API包括了两个文件:db_ora_oci_ux.h和db_ora_oci_ux.c。db_ora_oci_ux.h是头文件,而所有与数据库的交互操作的实现都是在db_ora_oci_ux.c中完成的。
本文对OCI的创建数据库连接操作的源码进行简单的剖析。
OCI中建立数据库连接的源码剖析
在OCI中,建立数据库连接的操作是由CDbCreateDb函数实现的,其代码如下:
void *CDbCreateDb(INT8 *pDbType, INT8*pServer, INT8 *pDbName, INT8 *pUser, INT8 *pPwd)
{
CDbRecordset *pcolbuf = NULL;
OCIHDBC hdbc = NULL;
CDb *hDb = NULL;
if (NULL == pServer)
{
WriteLog("CDbCreateDb: CDbCreateDb[0] failed", NULL, NULL);
return NULL;
}
/* 申请句柄指针空间 */
hDb = (CDb *)OsGetUB(sizeof(CDb));
if (NULL == hDb)
{
WriteLog("CDbCreateDb: CDbCreateDb[1] failed", NULL, NULL);
return NULL;
}
hDb->hdbc = NULL;
hDb->hRec = NULL;
pthread_mutex_lock(&s_dbmutex);
/* 初始化数据库连接 */
hdbc = DoDbInit((text *)pServer, (text *)pUser, (text *)pPwd);
if (NULL == hdbc)
{
pthread_mutex_unlock(&s_dbmutex);
OsRetUB((UINT8*)hDb);
return NULL;
}
/* 创建结果集 */
pcolbuf = DoRecInit();
if (NULL == pcolbuf)
{
pthread_mutex_unlock(&s_dbmutex);
OsRetUB((UINT8*)hDb);
DoDbFree(hdbc);
return NULL;
}
hDb->hdbc = hdbc;
hDb->hRec = pcolbuf;
hDb->iDbType = CDB_TYPE_ORACLE;
pthread_mutex_unlock(&s_dbmutex);
returnhDb;
}
从该函数的代码实现中,我们可以看到:
1)建立数据库连接包括这几步操作:第一步,申请句柄指针空间;第二步,初始化数据库连接;第三步,创建结果集。
2)申请句柄指针空间操作是由OsGetUB函数实现的,初始化数据库连接操作是由DoDbInit函数实现的,创建结果集操作是由DoRecInit函数实现的。
3)为了防止在多个流程中同时调用该函数,在初始化数据库连接之前采用了加锁操作,这保证了每一个创建数据库的操作所返回的句柄是唯一的。
4)如果初始化数据库连接操作函数DoDbInit执行失败了,程序就会执行OsRetUB函数来释放句柄指针空间(该操作与之前的申请句柄指针空间操作对应起来)。
5)如果创建结果集操作函数DoRecInit执行失败了,程序除了执行OsRetUB函数来释放句柄指针空间之外,还会执行DoDbFree函数来释放数据库连接(该操作与之前的初始化数据库连接操作对应起来)。
初始化数据库连接操作函数DoDbInit的代码如下:
static void *DoDbInit(text *dblink, text*uid, text *pwd)
{
OCIHDBC hdbc = NULL;
sword rc = (sword)0;
char errBuf[200];
sb4 errcode;
/* 申请所有句柄指针保存空间 */
hdbc = (OCIHDBC)OsGetUB(sizeof(t_envctx));
if (NULL == hdbc)
{
WriteLog("DoDbInit: OsGetUB failed", NULL, NULL);
return NULL;
}
/* 创建OCI环境 */
if (OCIInitialize((ub4)OCI_THREADED|OCI_OBJECT, (dvoid *)0
, (dvoid * (*)(dvoid *,size_t))0
, (dvoid * (*)(dvoid *, dvoid*, size_t))0
, (void (*)(dvoid *, dvoid*))0))
{
WriteLog("DoDbInit: OCIInitialize fail", NULL, NULL);
return NULL;
}
if(OCIEnvInit((OCIEnv **)&hdbc->envhp, (ub4)OCI_DEFAULT
, (size_t)0, (dvoid **)0))
{
WriteLog("DoDbInit: OCIEnvInit fail", NULL, NULL);
return NULL;
}
/*申请错误句柄 */
if(OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->errhp
, (ub4)OCI_HTYPE_ERROR,(size_t)0, (dvoid **)0))
{
WriteLog("DoDbInit: OCIHandleAlloc allocate errhp fail", NULL,NULL);
return NULL;
}
/* 申请服务器句柄 */
if(OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->srvhp
, (ub4)OCI_HTYPE_SERVER,(size_t)0, (dvoid **)0))
{
OCIErrorGet((dvoid *)hdbc->errhp, 1, NULL, &errcode,(text*)errBuf, (ub4)sizeof(errBuf), (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4) OCI_HTYPE_ERROR);
WriteLog("DoDbInit: OCIHandleAlloc allocate srvhp fail", NULL,NULL);
WriteLog(errBuf, NULL, NULL);
return NULL;
}
/* 申请服务环境句柄 */
if(OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->svchp
, (ub4)OCI_HTYPE_SVCCTX,(size_t)0, (dvoid * *)0))
{
OCIErrorGet((dvoid *)hdbc->errhp, 1, NULL, &errcode,(text*)errBuf, (ub4)sizeof(errBuf), (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4) OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4) OCI_HTYPE_ERROR);
WriteLog("DoDbInit: OCIHandleAlloc allocate svchp fail", NULL,NULL);
WriteLog(errBuf, NULL, NULL);
return NULL;
}
/* 连接数据库 */
if (OCIServerAttach(hdbc->srvhp, hdbc->errhp, dblink
, (sb4)strlen((char*)dblink), (ub4)OCI_DEFAULT))
{
/* 释放环境句柄,系统自动释放在其下所分配的所有其它句柄 */
OCIErrorGet((dvoid *)hdbc->errhp, 1, NULL, &errcode,(text*)errBuf, (ub4)sizeof(errBuf), (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
OsRetUB((UINT8*)hdbc);
WriteLog("DoDbInit: OCIServerAttach fail", NULL, NULL);
WriteLog(errBuf, NULL, NULL);
return NULL;
}
/* 设置服务环境的服务器属性 */
OCIAttrSet((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX
, (dvoid *)hdbc->srvhp, (ub4)0
, (ub4)OCI_ATTR_SERVER, hdbc->errhp);
/* 申请用户会话句柄 */
OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->authp
, (ub4)OCI_HTYPE_SESSION,(size_t)0, (dvoid **)0);
/* 设置会话所使用的用户帐户和密码 */
if (OCIAttrSet((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION
, (dvoid *)uid,(ub4)strlen((char *)uid)
, (ub4)OCI_ATTR_USERNAME,hdbc->errhp))
{
OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
OsRetUB((UINT8*)hdbc);
WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_USERNAME] fail", NULL,NULL);
return NULL;
}
if (OCIAttrSet((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION
, (dvoid *)pwd,(ub4)strlen((char *)pwd)
, (ub4)OCI_ATTR_PASSWORD,hdbc->errhp))
{
OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
OsRetUB((UINT8*)hdbc);
WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_PASSWORD] fail", NULL,NULL);
return NULL;
}
/* 建立数据库操作会话 */
if ( (rc = OCISessionBegin(hdbc->svchp, hdbc->errhp,hdbc->authp
, (ub4)OCI_CRED_RDBMS,(ub4)OCI_DEFAULT)))
{
DoDbErrProc(hdbc->errhp, rc, "OCISessionBegin");
OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
OsRetUB((UINT8*)hdbc);
WriteLog("DoDbInit: OCISessionBegin fail", NULL, NULL);
return NULL;
}
/* 设置会话服务环境 */
if (OCIAttrSet((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX
, (dvoid *)hdbc->authp,(ub4)0
, (ub4)OCI_ATTR_SESSION,hdbc->errhp))
{
OCISessionEnd(hdbc->svchp, hdbc->errhp, hdbc->authp, (ub4)0);
OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
OsRetUB((UINT8*)hdbc);
WriteLog("DoDbInit:OCIAttrSet[OCI_ATTR_SESSION] fail", NULL, NULL);
return NULL;
}
hdbc->stmthp = NULL;
return hdbc;
}
下面对DoDbInit函数进行分析:
1)该函数的执行流程是这样的:第一步,申请所有句柄指针保存空间;第二步,创建OCI环境;第三步,申请错误句柄;第四步,申请服务器句柄;第五步,申请服务环境句柄;第六步,连接数据库;第七步,设置服务环境的服务器属性;第八步,申请用户会话句柄,第九步,设置会话所使用的用户帐户和密码;第十步,建立数据库操作会话;第十一步,设置会话服务环境。
2)实现以上十一步操作的函数均是OCI底层提供的(都以OCI打头)。不管哪一步操作执行失败,都会输出相关的日志,可供排查问题。
3)所有OCI主要句柄数据结构OCIHDBC的实现如下:
/* 所有OCI主要句柄数据结构 */
typedef struct
{
OCIEnv *envhp; /* 环境句柄 */
OCIError *errhp; /* 错误句柄 */
OCIServer *srvhp; /* 服务器句柄 */
OCISvcCtx *svchp; /* 服务环境句柄 */
OCISession *authp; /* 会话句柄 */
OCIStmt *stmthp; /* 语句句柄 */
}t_envctx;
typedef t_envctx *OCIHDBC; /* 方便使用定义OCIHDBC数据类型 */
以上不同的操作是对OCIHDBC结构体中对应的句柄赋值。
创建结果集操作函数DoRecInit的代码如下:
static CDbRecordset *DoRecInit()
{
CDbRecordset *hRecordset;
hRecordset = (CDbRecordset *)OsGetUB(sizeof(CDbRecordset));
if (NULL == hRecordset)
{
WriteLog("DbInitRecordset: DbInitRecordset[0] fail", NULL,NULL);
return NULL;
}
memset((void *)hRecordset, 0, sizeof(CDbRecordset));
return hRecordset;
}
下面对DoRecInit函数进行分析:
1)该函数的作用是初始化结果集,首先,该函数执行OsGetUB函数申请句柄指针空间,然后执行memset函数初始化结果集。
2)结果集结构体CDbRecordset的代码如下:
typedef struct CDbRecordsetTag
{
void *cmd; /* 命令缓冲区 */
int sqltype; /* 1:select 2:other*/
int colCount; /* 返回列数 */
char colfieldname[CDB_MAX_COL_NUM][40];/* 每列列名 */
int colfieldlength[CDB_MAX_COL_NUM]; /* 列名宽度 */
int pColWidth[CDB_MAX_COL_NUM]; /* 每列宽度 */
int pColType[CDB_MAX_COL_NUM]; /* 列类型 */
char pRecordBuf[CDB_MAX_COL_NUM][CDB_MAX_COL_WIDTH];/* 列数据 */
int pRetColWidth[CDB_MAX_COL_NUM];
short pRetIndicator[CDB_MAX_COL_NUM];
} CDbRecordset;
如果后续操作要从数据库中获取数据,那么这些数据就用CDbRecordset结构体来存储。
释放数据库连接操作函数DoDbFree的代码如下:
static void DoDbFree(OCIHDBC hdbc)
{
if (NULL == hdbc)
{
return;
}
OCISessionEnd(hdbc->svchp, hdbc->errhp, hdbc->authp, (ub4)0);
OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
OsRetUB((UINT8*)hdbc);
hdbc = NULL;
}
从代码可以看出,该函数的功能是依次释放在DoDbInit函数中所申请的句柄指针。所有的以OCI开头的函数都是OCI底层提供的。
创建数据库连接函数CDbCreateDb的调用
在我们编写C代码创建数据库连接的时候,只需要将db_ora_oci_ux.h文件头包括进来,同时直接调用CDbCreateDb函数就可以了。
示例代码如下:
INT32 main(void)
{
INT8 szDBServerName[50] = {0};
INT8 szDBName[50] = {0};
INT8 szDBUser[50] = {0};
INT8 szDBPwd[50] = {0};
void *pDBHandle = NULL;
// 获取数据库各参数的值
memcpy(szDBServerName, "db10_10_10_10",strlen("db10_10_10_10"));
memcpy(szDBName, "dbp_166", strlen("dbp_166"));
memcpy(szDBUser, "dbp_166", strlen("dbp_166"));
memcpy(szDBPwd, "dbp_166", strlen("dbp_166"));
// 连接数据库
pDBHandle = CDbCreateDb("Oracle", szDBServerName, szDBName,szDBUser, szDBPwd);
if (pDBHandle == NULL) // 连接失败
{
printf("ConnectDB failed! ServiceName:%s, DBName:%s, User:%s,Pwd:%s", szDBServerName, szDBName, szDBUser, szDBPwd);
return -1;
}
printf("ConnectDB success! ServiceName:%s, DBName:%s, User:%s,Pwd:%s", szDBServerName, szDBName, szDBUser, szDBPwd);
return 0;
}
说明:
1)CDbCreateDb函数的五个输入参数分别是:数据库类型、数据库服务名、数据库名、用户名和密码。除了数据库类型之外,其他几个参数都和具体的数据库有关,需要在安装Oracle数据库的时候进行设置。
2)只有在数据库句柄分配成功(也就是数据库连接建立成功)的情况下,程序才能执行后续操作;如果数据库句柄分配失败,要及时找到失败原因。