C 驱动连接 OceanBase 数据库

本文介绍了如何通过 OBCI 驱动连接并使用 OceanBase 数据库。

前提条件

  • 确保设置了基本的数据库开发环境。

  • 确保满足如下硬件环境:

    • 硬件平台:x86_64。

    • 操作系统:CentOS/Redhat 系 Linux 发行版 7.2。

    • 编译器:GCC 4.8。

  • 联系技术支持人员获取 OBCI 和 LibOBClient 的 RPM 安装包。

C 驱动连接 OceanBase 数据库

步骤一:获取数据库连接参数

参考 获取连接参数 文档,获取相应的租户连接参数,例如:

obclient -hxxx.xxx.xxx.xxx -P1521 -u a**** -p******

数据库连接参数包含了访问数据库所需的参数信息,在验证示例代码前,可通过数据库连接参数验证登录数据库,保证信息正确。

参数说明:

  • -hOceanBase 数据库连接的域名。

  • -POceanBase 数据库连接端口,Oracle 模式租户默认是 1521。

  • -u:连接租户的账号。

  • -p:账号密码

步骤二:安装 C 相关驱动

当您获取 RPM 包后,在命令行工具中以 root 用户权限执行如下命令进行 OBCI 驱动的安装。

由于 OBCI 依赖于 LibOBClient,所以需要先安装 LibOBClient。

$ rpm -ivh libobclient-<version>.x86_64.rpm

再安装 OBCI。

$ rpm -ivh obci-<version>.x86_64.rpm

在默认情况下,软件包中包含的程序与文件将安装在如下路径中:

  • 头文件被安装在 /u01/obclient/include 路径下。

  • 库文件被安装在 /u01/obclient/lib 路径下。

在高版本 OBCI 安装包中,由于版权限制,连接过程需要使用的数据库文件需要安装 Oracle Instant Client 获取,详细下载请参考 Oracle Instant Client Downloads

常见需要的安装包为 basic 和 SDK 包。

  • 安装 basic 包。

    $ rpm -ivh oracle-instantclient-basic-<version>.x86_64.rpm
  • 安装 SDK 包

    $ rpm -ivh oracle-instantclient-devel-<version>.x86_64.rpm

步骤三:编写示例代码

本文通过具体实例介绍在 OceanBase 数据库 Oracle 模式下,C 语言通过 OBCI 与数据库服务器 OBServer 节点交互的基本方式。

  1. 初始化 OBCI 环境和线程。

    • 初始化 OBCI 程序环境

      OCIInitialize(OCI_DEFAULT, NULL, NULL, NULL, NULL)
    • 初始化环境句柄

      OCIEnvInit(&envhp, OCI_DEFAULT, 0, 0)
  2. 分配必要的句柄与数据结构。

    • 分配句柄

      OCIHandleAlloc(envhp, (dvoid **)&svchp, OCI_HTYPE_SVCCTX, 0, 0)
    • 服务器环境句柄

      OCIHandleAlloc(envhp, (dvoid **)&srvhp, OCI_HTYPE_SERVER, 0, 0)
    • 服务器句柄

      OCIHandleAlloc(envhp, (dvoid **)&authp, OCI_HTYPE_SESSION, 0, 0)
    • 会话句柄

      OCIHandleAlloc(envhp, (dvoid **)&errhp, OCI_HTYPE_ERROR, 0, 0)
    • 错误句柄

      OCIHandleAlloc(envhp, (dvoid **)&dschp, OCI_HTYPE_DESCRIBE, 0, 0)
  3. 建立与数据库的连接以及创建用户会话。

    • 设置用户名

       OCIAttrSet(authp, OCI_HTYPE_SESSION, (text *)strUserName,(ub4)strlen(strUserName), OCI_ATTR_USERNAME, errhp)
    • 设置密码

      OCIAttrSet(authp, OCI_HTYPE_SESSION, (text *)strPassword,(ub4)strlen(strPassword), OCI_ATTR_PASSWORD, errhp)
    • 设置服务器环境句柄属性

      OCIAttrSet((dvoid *)svchp, (ub4)OCI_HTYPE_SVCCTX,(dvoid *)srvhp, (ub4)0, OCI_ATTR_SERVER, errhp)
      OCIAttrSet(svchp, OCI_HTYPE_SVCCTX, (dvoid *)authp,0, OCI_ATTR_SESSION, errhp)
    • 创建会话

      OCISessionBegin(svchp, errhp, authp, OCI_CRED_RDBMS, OCI_DEFAULT)
    • 开始会话

      OCIHandleAlloc(envhp, (dvoid **)&stmthp, OCI_HTYPE_STMT, 0, 0)
  4. 通过 SQL 与 OceanBase 服务器交换数据,而后再做数据处理。一条 SQL 语句在 OBCI 应用程序中的执行步骤如下:

    1. 通过调用函数 OCIStmtPrepare() 或者 OCIStmtPrepare2() 准备 SQL 语句。

      OCIStmtPrepare(stmthp, errhp, (text *)sql, strlen(sql), OCI_NTV_SYNTAX,OCI_DEFAULT)
    2. 通过调用一个或者多个函数,如 OCIBindByPos() 把输入变量的地址绑定在 DML 语句中的占位符中。

      OCIBindByPos(stmthp, &bidhp[0], errhp, 1, &szpersonid,
       sizeof(szpersonid), SQLT_INT, NULL, NULL, NULL, 0, NULL, 0)

      或者通过 OCIBindByName() 函数进行。

      OCIBindByName(stmthp, &bidhp[0], errhp, (const OraText*)":personid", 9, &szpersonid,
       sizeof(szpersonid), SQLT_INT, NULL, NULL, NULL, 0, NULL, 0)
    3. 调用 OCIStmrExecute() 函数执行 SQL 语句。

      OCIStmtExecute(svchp, stmthp, errhp, (ub4)1, (ub4)0, (CONST OCISnapshot *)0, (OCISnapshot *)0, (ub4)OCI_DEFAULT)
    4. 调用 OCIDefineByPos() 函数为 SQL 语句中的数据输出项定义输出变量。

      OCIDefineByPos(stmthp, &defhp[0], errhp, 1, &szpersonid,
        sizeof(szpersonid), SQLT_INT, &ind[0], 0, 0, OCI_DEFAULT)
    5. 调用 OCIStmtFetch() 函数来获取查询的结果集。

      OCIStmtFetch(stmthp, errhp, 1, OCI_FETCH_NEXT, OCI_DEFAULT)
  5. 结束用户会话与断开与数据库的连接。

    • 结束会话

      OCISessionEnd(svchp, errhp, authp, (ub4)0)
    • 断开与数据库的连接

      OCIServerDetach(srvhp, errhp, OCI_DEFAULT)
  6. 释放在程序中所分配的句柄。

    OCIHandleFree((dvoid *)dschp, OCI_HTYPE_DESCRIBE)
    OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT)
    OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR)
    OCIHandleFree((dvoid *)authp, OCI_HTYPE_SESSION)
    OCIHandleFree((dvoid *)svchp, OCI_HTYPE_SVCCTX)
    OCIHandleFree((dvoid *)srvhp, OCI_HTYPE_SERVER)

示例代码

示例文件 test.c 代码内容如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include "oci.h"
/*声明句柄*/
OCIEnv *envhp; /*环境句柄*/
OCISvcCtx *svchp; /*服务环境句柄*/
OCIServer *srvhp; /*服务器句柄*/
OCISession *authp; /*会话句柄*/
OCIStmt *stmthp; /*语句句柄*/
OCIDescribe *dschp; /*描述句柄*/
OCIError *errhp; /*错误句柄*/
OCIDefine *defhp[3]; /*定义句柄*/
OCIBind *bidhp[4]; /*绑定句柄*/
sb2 ind[3]; /*指示符变量*/
/*绑定 select 结果集的参数*/
int szpersonid; /*存储 personid 列*/
text szname[51]; /*存储 name 列*/
text szemail[51]; /*存储 mail 列*/
char sql[256]; /*存储执行的 sql 语句*/

int main(int argc, char *argv[])
{
 char strServerName[50];
 char strUserName[50];
 char strPassword[50];
 /*设置服务器,用户名和密码*/
 strcpy(strServerName, "xxx.xxx.xxx.xxx:1521");
 strcpy(strUserName, "a****");
 strcpy(strPassword, "******");
 /*初始化 OCI 应用环境*/
 OCIInitialize(OCI_DEFAULT, NULL, NULL, NULL, NULL);
 /*初始化环境句柄*/
 OCIEnvInit(&envhp, OCI_DEFAULT, 0, 0);
 /*分配句柄*/
 OCIHandleAlloc(envhp, (dvoid **)&svchp, OCI_HTYPE_SVCCTX, 0, 0);
 /*服务器环境句柄*/
 OCIHandleAlloc(envhp, (dvoid **)&srvhp, OCI_HTYPE_SERVER, 0, 0);
 /*服务器句柄*/
 OCIHandleAlloc(envhp, (dvoid **)&authp, OCI_HTYPE_SESSION, 0, 0);
 /*会话句柄*/
 OCIHandleAlloc(envhp, (dvoid **)&errhp, OCI_HTYPE_ERROR, 0, 0);
 /*错误句柄*/
 OCIHandleAlloc(envhp, (dvoid **)&dschp, OCI_HTYPE_DESCRIBE, 0, 0);
 /*描述符句柄*/
 /*连接服务器*/
 OCIServerAttach(srvhp, errhp, (text *)strServerName, (sb4)strlen(strServerName), OCI_DEFAULT);
 /*设置用户名和密码*/
 OCIAttrSet(authp, OCI_HTYPE_SESSION, (text *)strUserName, (ub4)strlen(strUserName), OCI_ATTR_USERNAME, errhp);
 OCIAttrSet(authp, OCI_HTYPE_SESSION, (text *)strPassword, (ub4)strlen(strPassword), OCI_ATTR_PASSWORD, errhp);
 /*设置服务器环境句柄属性*/
 OCIAttrSet((dvoid *)svchp, (ub4)OCI_HTYPE_SVCCTX, (dvoid *)srvhp, (ub4)0, OCI_ATTR_SERVER, errhp);
 OCIAttrSet(svchp, OCI_HTYPE_SVCCTX, (dvoid *)authp, 0, OCI_ATTR_SESSION, errhp);
 /*创建并开始一个用户会话*/
 OCISessionBegin(svchp, errhp, authp, OCI_CRED_RDBMS, OCI_DEFAULT);
 OCIHandleAlloc(envhp, (dvoid **)&stmthp, OCI_HTYPE_STMT, 0, 0);
 /*语句句柄*/

 /************************************************************************/
 /*创建 person 表*/
 /************************************************************************/
 static text* SQL_CREATE_TB = (text*)"create table person(personid number, name varchar(256), email varchar(256))";
 /*准备 SQL 语句*/
 OCIStmtPrepare(stmthp, errhp, SQL_CREATE_TB, strlen((char *)SQL_CREATE_TB),OCI_NTV_SYNTAX, OCI_DEFAULT);
 /*执行 SQL 语句*/
 OCIStmtExecute(svchp, stmthp, errhp, 1, 0, 0, 0, OCI_DEFAULT);
 /*提交到数据库*/
 OCITransCommit(svchp, errhp, OCI_DEFAULT);

 /************************************************************************/
 /*插入数据*/
 /************************************************************************/
 memset(sql, 0, sizeof(sql));
 strcpy(sql, "insert into person values(:personid,:name,:email)");
 /*准备 SQL 语句*/
 OCIStmtPrepare(stmthp, errhp, (text *)sql, strlen(sql),OCI_NTV_SYNTAX, OCI_DEFAULT);
 /*绑定输入列*/
 OCIBindByName(stmthp, &bidhp[0], errhp, (const OraText*)":personid", 9, &szpersonid, sizeof(szpersonid), SQLT_INT, NULL, NULL, NULL, 0, NULL, 0);
 OCIBindByName(stmthp, &bidhp[2], errhp, (const OraText*)":name", 5, szname, sizeof(szname), SQLT_STR, NULL, NULL, NULL, 0, NULL, 0);
 OCIBindByName(stmthp, &bidhp[3], errhp, (const OraText*)":email", 6, szemail, sizeof(szemail), SQLT_STR, NULL, NULL, NULL, 0, NULL, 0);
 /*设置输入参数*/
 szpersonid = 1;
 memset(szname, 0, sizeof(szname));
 strcpy((char*)szname, "obtest");
 memset(szemail, 0, sizeof(szemail));
 strcpy((char*)szemail, "t@ob.com");
 /*执行 SQL 语句*/
 OCIStmtExecute(svchp, stmthp, errhp, (ub4)1, (ub4)0, (CONST OCISnapshot *)0, (OCISnapshot *)0, (ub4)OCI_DEFAULT);
 /*提交到数据库*/
 OCITransCommit(svchp, errhp, OCI_DEFAULT);

 /************************************************************************/
 /*查询 person 表*/
 /************************************************************************/
 strcpy(sql, "select personid ,name,email from person;");
 /*准备 SQL 语句*/
 OCIStmtPrepare(stmthp, errhp, (text *)sql, strlen(sql), OCI_NTV_SYNTAX, OCI_DEFAULT);
 /*绑定输出列*/
 OCIDefineByPos(stmthp, &defhp[0], errhp, 1, &szpersonid, sizeof(szpersonid), SQLT_STR, &ind[0], 0, 0, OCI_DEFAULT);
 OCIDefineByPos(stmthp, &defhp[1], errhp, 2, (ub1 *)szname, sizeof(szname), SQLT_STR, &ind[1], 0, 0, OCI_DEFAULT);
 OCIDefineByPos(stmthp, &defhp[2], errhp, 3, (ub1 *)szemail, sizeof(szemail), SQLT_STR, &ind[2], 0, 0, OCI_DEFAULT);
 /*执行 SQL 语句*/
 OCIStmtExecute(svchp, stmthp, errhp, (ub4)0, 0, NULL, NULL,
 OCI_DEFAULT);
 printf("%-10s%-10s%-10s\n", "PERSONID", "NAME", "email");
 while ((OCIStmtFetch(stmthp, errhp, 1, OCI_FETCH_NEXT, OCI_DEFAULT)) != OCI_NO_DATA)
 {
 printf("%-10d", szpersonid);
 printf("%-10s", szname);
 printf("%-10s\n", szemail);
 break;
 }
 /*提交到数据库*/
 OCITransCommit(svchp, errhp, OCI_DEFAULT);

 /************************************************************************/
 /*删除 person 表*/
 /************************************************************************/
 static text* SQL_DROP_TB = (text*)"drop table person";
 /*准备 SQL 语句*/
 OCIStmtPrepare(stmthp, errhp, (text*)SQL_DROP_TB, strlen((char *)SQL_DROP_TB), OCI_NTV_SYNTAX, OCI_DEFAULT);
 /*执行 SQL 语句*/
 OCIStmtExecute(svchp, stmthp, errhp, 1, 0, 0, 0, OCI_COMMIT_ON_SUCCESS);
 /*提交到数据库*/
 OCITransCommit(svchp, errhp, OCI_DEFAULT);
 
 //结束会话
 OCISessionEnd(svchp, errhp, authp, (ub4)0);
 //断开与数据库的连接
 OCIServerDetach(srvhp, errhp, OCI_DEFAULT);
 //释放OCI句柄
 OCIHandleFree((dvoid *)dschp, OCI_HTYPE_DESCRIBE);
 OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
 OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR);
 OCIHandleFree((dvoid *)authp, OCI_HTYPE_SESSION);
 OCIHandleFree((dvoid *)svchp, OCI_HTYPE_SVCCTX);
 OCIHandleFree((dvoid *)srvhp, OCI_HTYPE_SERVER);
 return 0;
}

修改代码中的数据库连接参数。参考如下字段,对应的值,则取自步骤一获取的数据库连接参数。

strcpy(strServerName, "xxx.xxx.xxx.xxx:1521");
strcpy(strUserName, "a****");
strcpy(strPassword, "******");
  • strServerName:取自 -h -P 参数,OceanBase 数据库连接的域名和端口。Oracle 模式租户默认端口是 1521。

  • strUserName:取自 -u 参数,连接租户的账号。

  • strPassword:取自 -p 参数,账号密码 。

步骤四:执行示例

  1. 代码编辑完成后,可以通过如下命令进行编译。

    $ gcc test.c -I/u01/obclient/include /u01/obclient/lib/libobci.a -L/usr/local/lib64 -lstdc++ -lpthread -ldl -lm -g -o test

    如果使用了高版本 obci,安装 Oracle Instant Client,-I 需要使用 Oracle 目录,如下:

    $ gcc test.c -I/usr/include/oracle/21/client64/ -L/u01/obclient/lib/ -L/usr/local/lib64 -lobci -lobclnt -g  -o test
  2. 编译完成后,指定运行路径。

    $ export LD_LIBRARY_PATH=/u01/obclient/lib
  3. 运行代码示例。

    $ ./test
  4. 运行得到如下结果,说明数据库连接成功且语句执行正常。

    PERSONID  NAME      email
    49        obtest    t@ob.com