概述
在笔者所开发过的产品中,有很多都需要与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); 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*,(void (*)(dvoid *,dvoid*))0)) { WriteLog("DoDbInit: OCIInitialize fail",NULL); return NULL; } if(OCIEnvInit((OCIEnv **)&hdbc->envhp,(ub4)OCI_DEFAULT,(size_t)0,(dvoid **)0)) { WriteLog("DoDbInit: OCIEnvInit fail",NULL); return NULL; } /*申请错误句柄 */ if(OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->errhp,(ub4)OCI_HTYPE_ERROR,(dvoid **)0)) { WriteLog("DoDbInit: OCIHandleAlloc allocate errhp fail",NULL); return NULL; } /* 申请服务器句柄 */ if(OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->srvhp,(ub4)OCI_HTYPE_SERVER,(dvoid **)0)) { OCIErrorGet((dvoid *)hdbc->errhp,1,&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); WriteLog(errBuf,NULL); return NULL; } /* 申请服务环境句柄 */ if(OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->svchp,(ub4)OCI_HTYPE_SVCCTX,(dvoid * *)0)) { OCIErrorGet((dvoid *)hdbc->errhp,(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); return NULL; } /* 连接数据库 */ if (OCIServerAttach(hdbc->srvhp,hdbc->errhp,dblink,(sb4)strlen((char*)dblink),(ub4)OCI_DEFAULT)) { /* 释放环境句柄,系统自动释放在其下所分配的所有其它句柄 */ OCIErrorGet((dvoid *)hdbc->errhp,(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); return NULL; } /* 设置服务环境的服务器属性 */ OCIAttrSet((dvoid *)hdbc->svchp,(dvoid *)hdbc->srvhp,(ub4)0,(ub4)OCI_ATTR_SERVER,hdbc->errhp); /* 申请用户会话句柄 */ OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->authp,(ub4)OCI_HTYPE_SESSION,(dvoid **)0); /* 设置会话所使用的用户帐户和密码 */ if (OCIAttrSet((dvoid *)hdbc->authp,(dvoid *)uid,(ub4)strlen((char *)uid),(ub4)OCI_ATTR_USERNAME,hdbc->errhp)) { OCIServerDetach(hdbc->srvhp,(ub4)OCI_DEFAULT); OCIHandleFree((dvoid *)hdbc->srvhp,(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); return NULL; } if (OCIAttrSet((dvoid *)hdbc->authp,(dvoid *)pwd,(ub4)strlen((char *)pwd),(ub4)OCI_ATTR_PASSWORD,(ub4)OCI_HTYPE_ENV); OsRetUB((UINT8*)hdbc); WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_PASSWORD] fail",NULL); return NULL; } /* 建立数据库操作会话 */ if ( (rc = OCISessionBegin(hdbc->svchp,hdbc->authp,(ub4)OCI_CRED_RDBMS,(ub4)OCI_DEFAULT))) { DoDbErrProc(hdbc->errhp,rc,"OCISessionBegin"); OCIServerDetach(hdbc->srvhp,(ub4)OCI_HTYPE_ENV); OsRetUB((UINT8*)hdbc); WriteLog("DoDbInit: OCISessionBegin fail",NULL); return NULL; } /* 设置会话服务环境 */ if (OCIAttrSet((dvoid *)hdbc->svchp,(dvoid *)hdbc->authp,(ub4)OCI_ATTR_SESSION,hdbc->errhp)) { OCISessionEnd(hdbc->svchp,(ub4)0); OCIServerDetach(hdbc->srvhp,(ub4)OCI_HTYPE_ENV); OsRetUB((UINT8*)hdbc); WriteLog("DoDbInit:OCIAttrSet[OCI_ATTR_SESSION] fail",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); return NULL; } memset((void *)hRecordset,0,sizeof(CDbRecordset)); return hRecordset; }
下面对DoRecInit函数进行分析:
1)该函数的作用是初始化结果集,首先,该函数执行OsGetUB函数申请句柄指针空间,然后执行memset函数初始化结果集。