访问www.tomcoding.com网站,学习Oracle内部数据结构,详细文档说明,下载Oracle的exp/imp,DUL,logminer,ASM工具的源代码,学习高技术含量的内容。

上一节我们介绍了LOB定位符的绑定和定义操作,这里重点强调一下定义操作,这个行为一般用于LOB SELECT操作,LOB SELECT操作是所有LOB操作的基础,所有的关于LOB操作的OCI函数都是针对LOB定位符的,LOB SELECT的目的就是为了得到一个LOB定位符,一个LOB表的每行数据的每个LOB字段都对应一个LOB定位符,所以处理一行数据的多个LOB字段也要得到多个LOB定位符。下面看看LOB写操作的步骤。

1. 准备LOB SELECT语句,一般SQL语句文本为SELECT lob_column FROM lob_table WHERE XXXX=XXXX FOR UPDATE。

2. 定义输出的LOB定位符。这个定位符在之前要分配好,不用设置为空LOB。

3. 执行LOB SELECT语句。

4. 执行OCIStmtFetch()操作,把LOB定位符和LOB字段关联起来。

5. 打开LOB定位符,使用OCILobOpen()函数。

6. 写入LOB数据,可以循环写入多次LOB数据,使用OCILobWrite2()函数。

7. 关闭LOB定位符,使用OCILobClose()函数。

8. 提交数据库改变。

先看看用到的OCI函数原型和参数。

打开LOB函数。

sword OCILobOpen ( OCISvcCtx *svchp,
    OCIError           *errhp,
    OCILobLocator      *locp,
    ub1                mode );

svchp是一个输入参数,是OCI服务上下文句柄。

errhp是一个输入/输出参数,错误句柄,用于返回错误码和错误信息文本。

locp是一个输入/输出参数,需要打开的LOB定位符。

mode是一个输入参数,打开的方式,取值OCI_LOB_READONLY或OCI_LOB_READWRITE。

关闭LOB函数。

sword OCILobClose ( OCISvcCtx *svchp,
    OCIError           *errhp,
    OCILobLocator      *locp );

svchp是一个输入参数,是OCI服务上下文句柄。

errhp是一个输入/输出参数,错误句柄,用于返回错误码和错误信息文本。

locp是一个输入/输出参数,需要关闭的LOB定位符。

写LOB函数。

sword OCILobWrite2 ( OCISvcCtx *svchp,
    OCIError           *errhp,
    OCILobLocator *locp,
    oraub8              *byte_amtp,
    oraub8              *char_amtp,
    oraub8              offset,
    void                *bufp,
    oraub8              buflen,
    ub1                 piece,
    void                *ctxp,
    OCICallbackLobWrite2 (cbfp)
    (
        void     *ctxp,
        void     *bufp,
        oraub8   *lenp,
        ub1      *piecep
        void     **changed_bufpp,
        oraub8   *changed_lenp
    )
    ub2                  csid,
    ub1                  csfrm );

svchp是一个输入/输出参数,是OCI服务上下文句柄。

errhp是一个输入/输出参数,错误句柄,用于返回错误码和错误信息文本。

locp是一个输入/输出参数,需要操作的LOB定位符,唯一引用一个LOB。

byte_amtp是一个输入/输出参数,写入LOB的字节数,在BLOB写入时使用。

char_amtp是一个输入/输出参数,写入LOB的字符个数,在CLOB写入时使用,BLOB写入时忽略这个参数。

offset是一个输入参数,写入LOB的绝对偏移量,从LOB头开始计算。对CLOB计算单位是字符,对BLOB计算单位是字节。offset的位置从1开始计算。如果使用流写入方式,只需要在第一次调用写函数时设置offset值,后续的写函数可以忽略offset,函数会自动判断。

bufp是一个输入参数,是写入的LOB数据的缓冲区指针。

buflen是一个输入参数,表示LOB数据缓冲区中数据的大小,以字节计算。

piece是一个输入参数,表示写入的是哪个数据片。取值为OCI_ONE_PIECE,流模式中为OCI_FIRST_PIECE,OCI_NEXT_PIECE和OCI_LAST_PIECE。

ctxp是一个输入参数,是回调函数的上下文指针,可以设置为NULL。

cbfp是一个输入参数,是回调函数的函数指针,不使用回调函数设置为NULL。

csid是一个输入参数,是缓冲区中数据的字符集ID,只对CLOB起作用。

csfrm是一个输入参数,是缓冲区数据的字符集形式,取值SQLCS_IMPLICIT表示与数据库字符集一致,取值SQLCS_NCHAR表示使用国际字符集。

我们使用上一节中创建的表test_clob_tab,里面已经插入一条空LOB数据,现在看看怎样写入LOB数据,我们向LOB中写入20次,每次写入4000字符,使用流模式写入。代码如下。

OCIEnv		  *envhp = NULL;
OCIError	  *errhp = NULL;
OCIServer  	*svrhp = NULL;
OCISession	*usrhp = NULL;
OCISvcCtx	  *svchp = NULL;
OCIStmt		  *smthp = NULL;int write_to_lob(void){int              i;sword	           rc;sb4		           ec;ub1              piece;int		           slen;oraub8           amt;OCIDefine        *defp;OCILobLocator    *locp;char	           sqltxt[1024];text             errbuf[512];char             buf[4096];/* 分配LOB定位符 */rc = OCIDescriptorAlloc((const void *)envhp, (void **)&locp,OCI_DTYPE_LOB, 0, (void **)NULL);if (rc != OCI_SUCCESS) {OCIErrorGet(envhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCIDescriptorAlloc() - [%d] %s\n", ec, errbuf);return (-1);}/* 下面执行LOB SELECT操作 */strcpy(sqltxt, "SELECT MESSAGE FROM test_clob_tab WHERE ID=1 FOR UPDATE");slen = strlen(sqltxt);rc = OCIStmtPrepare(smthp, errhp, (const OraText *)sqltxt, slen,OCI_NTV_SYNTAX, OCI_DEFAULT);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCIStmtPrepare() - [%d] %s\n", ec, errbuf);return (-1);}/* 定义LOB定位符输出 */rc = OCIDefineByPos((OCIStmt *)smthp,(OCIDefine **)&defp,(OCIError *)errhp,(ub4)1,(void *)&locp,(sb4)sizeof(OCILobLocator *),(ub2)SQLT_CLOB,(void *)NULL,(ub2 *)NULL,(ub2 *)NULL,(ub4)OCI_DEFAULT);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCIDefineByPos() - [%d] %s\n", ec, errbuf);return (-1);}/* 执行语句 */rc = OCIStmtExecute(svchp,smthp,		      /* stmthp */errhp,		      /* errhp */0,			        /* iters */0,			        /* rowoff */NULL,		      /* snap_in */NULL,		      /* snap_out */OCI_DEFAULT);	/* mode */if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCIExecute() - [%d] %s\n", ec, errbuf);return (-1);}/* 执行fetch操作 */rc = OCIStmtFetch(smthp, errhp, 1, OCI_FETCH_NEXT, OCI_DEFAULT);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCIStmtFetch() - [%d] %s\n", ec, errbuf);return (-1);}/* 打开LOB */rc = OCILobOpen(svchp, errhp, locp, OCI_LOB_READWRITE);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCILobOpen() - [%d] %s\n", ec, errbuf);return (-1);}for (i=0; i<20; i++) {if (i == 0)piece = OCI_FIRST_PIECE;else if (i == 19)piece = OCI_LAST_PIECE;elsepiece = OCI_NEXT_PIECE;sprintf(buf, "%02d", i);memset(&buf[2], 3998, 'A');amt = 4000;rc = OCILobWrite2(svchp, errhp, locp, NULL, &amt, 1,buf, 4000, piece, 0, SQLCS_IMPLICIT);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCILobWrite2() - [%d] %s\n", ec, errbuf);return (-1);}}/* 关闭LOB */rc = OCILobClose(svchp, errhp, locp);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCILobClose() - [%d] %s\n", ec, errbuf);return (-1);}/* 提交改变 */rc = OCITransCommit(svchp, errhp, OCI_DEFAULT);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCITransCommit() - [%d] %s\n", ec, errbuf);return (-1);}/* 释放LOB定位符 */rc = OCIDescriptorFree((void *)locp, OCI_DTYPE_LOB);if (rc != OCI_SUCCESS) {OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);fprintf(stderr, "OCIDescriptorFree() - [%d] %s\n", ec, errbuf);return (-1);}return (0);
}

在上面的写入中使用了流模式,在第一次写入时使用OCI_FIRST_PIECE,最后一次写入使用OCI_LAST_PIECE,其他的写入使用OCI_NEXT_PIECE,只需要在第一次写入时指定写入偏移量1,就可以从头写入数据,后续写操作的偏移量都被忽略了。写入函数的csid设置为0,数据使用环境变量NLS_LANG定义的字符集。

如果不使用流模式写入,那么写入操作使用OCI_ONE_PIECE,每次写入一片数据,每次要自己计算偏移量的位置,好处是可以随机向任意位置写入数据。

在写数据前要打开LOB,写完后要关闭LOB,跟写一个文件相似。如果不打开LOB也可以直接调用写函数,不过每次写操作Oracle还是会隐式的打开LOB,写完一次后隐式关闭LOB,这样在大量多次写入时会影响效率,所以在写之前打开LOB是一个好习惯。