Oracle调用接口(OCI)源码剖析(1):创建数据库连接

概述
在笔者所开发过的产品中,有很多都需要与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)只有在数据库句柄分配成功(也就是数据库连接建立成功)的情况下,程序才能执行后续操作;如果数据库句柄分配失败,要及时找到失败原因。

相关内容推荐