杰瑞科技汇

Oracle C 教程,如何快速入门?

Oracle C 编程教程 (OCI - Oracle Call Interface)

当我们说“Oracle C”,我们通常指的是使用 Oracle Call Interface (OCI) 来进行 C 语言编程,OCI 是 Oracle 提供的一套 C 语言函数库,它允许应用程序直接与 Oracle 数据库服务器进行通信,执行 SQL 语句、管理事务、处理数据等。

Oracle C 教程,如何快速入门?-图1
(图片来源网络,侵删)

为什么使用 OCI?

相比于其他数据库接口(如 JDBC, ODBC),OCI 有其独特的优势:

  1. 高性能:OCI 是一个底层接口,直接与数据库通信,开销小,速度快,适合对性能要求极高的场景,如高并发的 OLTP 系统。
  2. 功能强大:提供了对 Oracle 数据库几乎所有特性的支持,包括高级数据类型(如对象类型、集合类型)、PL/SQL 执行、高级队列、事务管理等。
  3. 细粒度控制:开发者可以精确地控制内存管理、网络连接、数据类型转换等,实现高度优化的应用程序。
  4. 跨平台:OCI 库在所有主流操作系统(Windows, Linux, Unix)上都有提供。

环境准备

在开始之前,你需要准备好以下环境:

  1. Oracle 客户端

    • 你需要安装 Oracle 客户端软件,最常用的版本是 Oracle Instant Client,它是一个轻量级的、无需安装的客户端包,非常适合开发。
    • 从 Oracle 官网下载适合你操作系统的 Instant Client。
    • 下载后,解压到一个目录(C:\oracle\instantclient_19_10)。
    • 关键步骤:将这个解压目录添加到系统的 PATH 环境变量中,这样,编译器和程序在运行时就能找到 OCI 的头文件和库文件。
  2. C 编译器

    Oracle C 教程,如何快速入门?-图2
    (图片来源网络,侵删)
    • Windows: Visual Studio (cl.exe) 或 MinGW (gcc)。
    • Linux: GCC (通常是默认安装的)。
  3. 头文件和库文件

    • 头文件: oci.h, oratypes.h, ol0def.h 等,它们通常位于 Instant Client 的 include 目录。
    • 库文件: oci.lib (Windows) 或 libclntsh.so (Linux),它们通常位于 Instant Client 的 lib 目录。

OCI 编程核心概念

OCI 程序的结构非常清晰,遵循一个典型的生命周期。

核心数据结构

  • OCIEnv (OCI Environment Handle):OCI 环境句柄,它是所有 OCI 操作的根,用于管理全局资源,如内存分配、错误处理等,一个 OCI 程序通常只需要一个环境句柄。
  • OCIError (OCI Error Handle):错误句柄,用于获取 OCI 调用中发生的错误信息,几乎所有的 OCI 函数都需要一个错误句柄作为参数。
  • OCISvcCtx (OCI Service Context Handle):服务上下文句柄,这是最重要的句柄之一,它封装了与数据库会话相关的所有信息,包括用户会话、服务器连接等,你执行 SQL 语句时,都需要这个句柄。
  • OCIStmt (OCI Statement Handle):语句句柄,代表一个已解析的 SQL 语句或 PL/SQL 块,你需要用它来准备、绑定、定义和执行 SQL。
  • OCIDefine (OCI Define Handle):定义句柄,用于将查询结果集中的列数据绑定到应用程序的变量上。
  • OCIBind (OCI Bind Handle):绑定句柄,用于将应用程序变量绑定到 SQL 语句中的占位符(如 var)。

OCI 编程基本流程

一个典型的 OCI 程序执行流程如下:

  1. 初始化:创建环境句柄 和错误句柄。
  2. 登录:建立到数据库服务器的连接,并创建服务上下文句柄。
  3. 准备 SQL:创建语句句柄,并使用 OCIStmtPrepare 准备 SQL 语句。
  4. 绑定变量 (可选):SQL 语句有输入占位符,使用 OCIBindByPosOCIBindByName 将 C 变量绑定到占位符。
  5. 定义变量 (可选):SQL 语句是查询,使用 OCIDefineByPos 将结果集中的列定义到 C 变量。
  6. 执行 SQL:使用 OCIStmtExecute 执行语句。
  7. 获取数据:对于查询,使用 OCIStmtFetch 获取多行数据。
  8. 处理结果:处理从数据库获取的数据。
  9. 清理资源:按照创建的逆序,依次释放语句句柄、服务上下文句柄、错误句柄和环境句柄。
  10. 登出:断开数据库连接。

OCI 编程实例 (Hello World)

下面是一个最简单的 OCI 程序示例,它连接到数据库,执行一个 SELECT 'Hello, OCI!' FROM DUAL 语句,并打印结果。

Oracle C 教程,如何快速入门?-图3
(图片来源网络,侵删)

代码示例 (oci_hello.c)

#include <stdio.h>
#include <stdlib.h>
// 包含 OCI 头文件
#include <oci.h>
// 定义错误处理宏
#define CHECKERR(err, handle, status) \
    if (status != OCI_SUCCESS) { \
        sb4 errcode; \
        text errbuf[512]; \
        OCIErrorGet((dvoid *)handle, (ub4)1, (text *)NULL, &errcode, \
                    errbuf, (ub4)sizeof(errbuf), OCI_HTYPE_ERROR); \
        fprintf(stderr, "Oracle Error: %s\n", errbuf); \
        exit(1); \
    }
int main() {
    // 1. 声明所有需要的句柄和变量
    OCIEnv *envhp = NULL;          // 环境句柄
    OCIError *errhp = NULL;        // 错误句柄
    OCISvcCtx *svchp = NULL;       // 服务上下文句柄
    OCIServer *srvhp = NULL;       // 服务器句柄
    OCIStmt *stmthp = NULL;        // 语句句柄
    OCIDefine *defnp = NULL;       // 定义句柄
    // 用于存储连接信息的变量
    text *dbname = (text *)""; // 数据库服务名,如 "localhost/XE"
    text *username = (text *)"your_username"; // 你的数据库用户名
    text *password = (text *)"your_password"; // 你的数据库密码
    // 用于存储查询结果的变量
    OCIString *hello_str = NULL;   // OCI 字符串类型
    text hello_buf[100];           // C 字符串缓冲区
    sword status; // OCI 函数返回的状态码
    // 2. 初始化 OCI 环境
    // OCI_OBJECT 模式允许我们使用 OCI 的对象类型(如 OCIString)
    status = OCIEnvCreate((OCIEnv **) &envhp, OCI_OBJECT, (dvoid *)0,
                          (size_t) 0, (dvoid **) 0, (size_t) 0,
                          (dvoid **) 0, (size_t) 0);
    CHECKERR(errhp, errhp, status);
    // 3. 创建错误句柄
    status = OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp,
                            OCI_HTYPE_ERROR, (size_t) 0, (dvoid **) 0);
    CHECKERR(errhp, errhp, status);
    // 4. 创建服务器句柄
    status = OCIHandleAlloc((dvoid *) envhp, (dvoid **) &srvhp,
                            OCI_HTYPE_SERVER, (size_t) 0, (dvoid **) 0);
    CHECKERR(errhp, errhp, status);
    // 5. 连接到数据库
    status = OCIServerAttach(srvhp, errhp, dbname, strlen((char *)dbname),
                             OCI_DEFAULT);
    CHECKERR(errhp, errhp, status);
    // 6. 创建会话句柄并附加到服务上下文
    OCIAuthInfo *authp = NULL;
    status = OCIHandleAlloc((dvoid *) envhp, (dvoid **) &authp,
                            OCI_HTYPE_SESSION, (size_t) 0, (dvoid **) 0);
    CHECKERR(errhp, errhp, status);
    // 设置用户名和密码
    status = OCIAttrSet((dvoid *) authp, OCI_HTYPE_SESSION,
                        (dvoid *) username, strlen((char *)username),
                        OCI_ATTR_USERNAME, errhp);
    CHECKERR(errhp, errhp, status);
    status = OCIAttrSet((dvoid *) authp, OCI_HTYPE_SESSION,
                        (dvoid *) password, strlen((char *)password),
                        OCI_ATTR_PASSWORD, errhp);
    CHECKERR(errhp, errhp, status);
    // 创建服务上下文句柄
    status = OCIHandleAlloc((dvoid *) envhp, (dvoid **) &svchp,
                            OCI_HTYPE_SVCCTX, (size_t) 0, (dvoid **) 0);
    CHECKERR(errhp, errhp, status);
    // 将服务器句柄附加到服务上下文
    status = OCIAttrSet((dvoid *) svchp, OCI_HTYPE_SVCCTX,
                        (dvoid *) srvhp, (ub4) 0, OCI_ATTR_SERVER, errhp);
    CHECKERR(errhp, errhp, status);
    // 将会话句柄附加到服务上下文
    status = OCIAttrSet((dvoid *) svchp, OCI_HTYPE_SVCCTX,
                        (dvoid *) authp, (ub4) 0, OCI_ATTR_SESSION, errhp);
    CHECKERR(errhp, errhp, status);
    // 7. 准备 SQL 语句
    text *sql = (text *)"SELECT 'Hello, OCI!' FROM DUAL";
    status = OCIHandleAlloc((dvoid *) envhp, (dvoid **) &stmthp,
                            OCI_HTYPE_STMT, (size_t) 0, (dvoid **) 0);
    CHECKERR(errhp, errhp, status);
    status = OCIStmtPrepare(stmthp, errhp, sql, strlen((char *)sql),
                            OCI_NTV_SYNTAX, OCI_DEFAULT);
    CHECKERR(errhp, errhp, status);
    // 8. 定义输出变量
    // 定义一个 OCICallbackDefine 结构,用于将结果映射到 OCIString
    OCICallbackDefine cbdef;
    cbdef.defnvop = (OCIDefineCallback *)OCIObjectPin;
    cbdef.lob = (OCILobLocator *)0;
    status = OCIDefineByPos(stmthp, &defnp, errhp, (ub4)1,
                             (dvoid *)&hello_str, (sb4)sizeof(OCIString),
                             SQLT_STR, (dvoid *)0, (ub2 *)0, (ub2 *)0,
                             OCI_DEFAULT, (OCICallbackDefine *)&cbdef);
    CHECKERR(errhp, errhp, status);
    // 9. 执行 SQL 语句
    status = OCIStmtExecute(svchp, stmthp, errhp, (ub4)1, (ub4)0,
                            (CONST OCISnapshot *)NULL, (OCISnapshot *)NULL,
                            OCI_DEFAULT);
    CHECKERR(errhp, errhp, status);
    // 10. 获取数据并打印
    // OCIStmtExecute 已经获取了数据,我们只需要从 OCIString 转换为 C 字符串
    if (hello_str) {
        status = OCIObjectGet((OCIEnv *)envhp, (OCIError *)errhp,
                              (OCIInd *)0, (dvoid **)&hello_str,
                              (text *)hello_buf, (ub4)sizeof(hello_buf),
                              OCI_DURATION_DEFAULT, OCI_TYPE_GET_CHAR);
        CHECKERR(errhp, errhp, status);
        printf("Result from Oracle: %s\n", hello_buf);
    }
    // 11. 清理资源 (非常重要!)
    // 按照与创建相反的顺序释放句柄
    if (stmthp) OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
    if (defnp) OCIDefineFree(defnp);
    if (svchp) {
        OCISessionEnd(svchp, errhp, authp, OCI_DEFAULT);
        OCIHandleFree((dvoid *)svchp, OCI_HTYPE_SVCCTX);
    }
    if (srvhp) OCIServerDetach(srvhp, errhp, OCI_DEFAULT);
    if (srvhp) OCIHandleFree((dvoid *)srvhp, OCI_HTYPE_SERVER);
    if (authp) OCIHandleFree((dvoid *)authp, OCI_HTYPE_SESSION);
    if (errhp) OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR);
    if (envhp) OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV);
    return 0;
}

如何编译 (以 Linux 和 GCC 为例)

假设你的 Instant Client 解压在 /usr/lib/oracle/19.10/client64 目录下。

# 设置编译器和链接器标志
export ORACLE_HOME=/usr/lib/oracle/19.10/client64
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH
# 编译命令
gcc -o oci_hello oci_hello.c -I$ORACLE_HOME/include -L$ORACLE_HOME/lib -lclntsh -lons
# 运行
./oci_hello

注意:Windows 下的编译类似,你需要使用 cl.exe 并指定 /I (include 路径) 和 /link (lib 路径) 参数,并链接 oci.libclntsh.lib

进阶主题

当你掌握了基础后,可以学习更高级的主题:

  1. 绑定输入变量

    • 修改 SQL 为 INSERT INTO my_table (id, name) VALUES (:id, :name)
    • 使用 OCIBindByPos 将 C 语言的 int idchar* name 变量绑定到 idname 占位符。
    • 执行 OCIStmtExecute 时,数据会自动从你的 C 变量传入数据库。
  2. 处理游标和获取多行数据

    • 对于返回多行的查询(如 SELECT * FROM employees),你需要使用 OCIStmtFetch
    • 在循环中调用 OCIStmtFetch,直到返回 OCI_NO_DATA 表示所有数据都已获取。
  3. 处理 LOB (Large Object)

    • OCI 提供了专门的函数(如 OCILobRead, OCILobWrite)来读写 CLOB, BLOB 等大型数据对象。
  4. 执行 PL/SQL 块

    你可以准备一个完整的 PL/SQL 块字符串,然后像普通 SQL 一样执行,这对于存储过程和函数调用非常有用。

  5. 对象类型和集合

    • OCI 支持将 Oracle 的对象类型(OBJECT)和集合类型(VARRAY, NESTED TABLE)映射到 C 语言的复杂数据结构。

学习资源

  • 官方文档Oracle Database Call Interface Programmer's Guide,这是最权威、最全面的资料,包含了所有函数的详细说明和示例,你可以在 Oracle 官网找到它。
  • Metalink (MOS - My Oracle Support):如果你有 Oracle 支持合同,MOS 是解决疑难杂症的最佳去处。
  • 社区和论坛:OTN (Oracle Technology Network) 论坛、Stack Overflow 上有大量关于 OCI 的问题和解答。

OCI 是一把强大的“瑞士军刀”,但它也相对复杂,学习曲线较陡,需要开发者对 C 语言和 Oracle 数据库都有较深的理解。

学习建议

  1. 从环境配置和简单查询开始,确保你能成功连接和获取数据。
  2. 深入理解句柄的概念和生命周期,这是 OCI 编程的核心。
  3. 掌握错误处理OCIErrorGet 是你最好的朋友。
  4. 逐步练习:从 SELECTINSERT/UPDATE/DELETE,再到绑定变量、处理游标,最后挑战 PL/SQL 和 LOB。

希望这份教程能帮助你顺利入门 Oracle C 编程!

分享:
扫描分享到社交APP
上一篇
下一篇