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

简介: 概述 在笔者所开发过的产品中,有很多都需要与Oracle数据库打交道。为了实现C代码与Oracle数据库的消息交互,Oracle公司为广大的开发者们提供了一个统一的调用接口OCI(Oracle Call Interface)。

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

目录
相关文章
|
11天前
|
DataWorks Oracle 关系型数据库
DataWorks操作报错合集之尝试从Oracle数据库同步数据到TDSQL的PG版本,并遇到了与RAW字段相关的语法错误,该怎么处理
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
29 0
|
11天前
|
SQL 关系型数据库 MySQL
【Go语言专栏】使用Go语言连接MySQL数据库
【4月更文挑战第30天】本文介绍了如何使用Go语言连接和操作MySQL数据库,包括选择`go-sql-driver/mysql`驱动、安装导入、建立连接、执行SQL查询、插入/更新/删除操作、事务处理以及性能优化和最佳实践。通过示例代码,展示了连接数据库、使用连接池、事务管理和性能调优的方法,帮助开发者构建高效、稳定的Web应用。
|
1天前
|
Oracle Java 关系型数据库
【服务器】python通过JDBC连接到位于Linux远程服务器上的Oracle数据库
【服务器】python通过JDBC连接到位于Linux远程服务器上的Oracle数据库
13 6
|
1天前
|
SQL Oracle 关系型数据库
零基础入门 Oracle数据库:轻松上手
零基础入门 Oracle数据库:轻松上手
4 0
|
1天前
|
SQL Java 关系型数据库
【JAVA基础篇教学】第十六篇:Java连接和操作MySQL数据库
【JAVA基础篇教学】第十六篇:Java连接和操作MySQL数据库
|
1天前
|
Oracle 关系型数据库 Java
java操作多数据源将oracle数据同步达梦数据库
java操作多数据源将oracle数据同步达梦数据库
|
2天前
|
存储 Oracle 关系型数据库
oracle 数据库 迁移 mysql数据库
将 Oracle 数据库迁移到 MySQL 是一项复杂的任务,因为这两种数据库管理系统具有不同的架构、语法和功能。
15 0
|
3天前
|
SQL Java 数据库连接
Java数据库编程实践:连接与操作数据库
Java数据库编程实践:连接与操作数据库
8 0
|
4天前
|
关系型数据库 Java 数据库
docker部署postgresql数据库和整合springboot连接数据源
docker部署postgresql数据库和整合springboot连接数据源
13 0
|
5天前
|
SQL JSON 关系型数据库
[UE虚幻引擎插件DTPostgreSQL] PostgreSQL Connector 使用蓝图连接操作 PostgreSQL 数据库说明
本插件主要是支持在UE蓝图中连接和操作PostgreSQL 数据库。
13 2

推荐镜像

更多