摘 要: OCI(Oracle Call Interface)是Oracle公司提供的开发基于Oracle数据库应用程序的底层接口,它具有速度快、支持第三代编程语言、对Oracle数据库的控制功能强等优点。本文首先介绍了OCI接口的优点、应用范围、OCI程序结构,接着详细介绍了在MiroSoft VC++6.0中应用OCI来访问Oracle数据库,以实例的方式演示了SQL语句的执行过程,并且结合实例对OCI的一些函数与处理SQL语句的方法做了说明。
关键词: OCI,VC++6.0, SQL,Oracle数据库引言
开发基于Oracle数据库的应用程序,我们可以选择多种工具,不仅可以用一般的数据库开发技术,诸如ADO(ActiveX Data Objects)、ODBC(Open DataBase Connectivity)等等,同时,也可以用Oracle公司提供的专门的开发工具,诸如Pro C_C++,OCI(Oracle Call Intedace)等等。比较这几种方式,前者因为是通用技术,开发起来比较容易,但是有一个致命的弱点就是诸如ADO之类的通用技术的速度太慢,如果我们要开发管理海量数据的数据库,比如影像数据库,那么,这种速度我们是不能忍受的。而OCI虽然开发起来难度大一些,但是它的速度极快,而且是一种底层接口,几乎可以操纵Oracle数据库的任何对象。
一、OCI简介
1.OCI概述
OCI(Oracle Call Intedace,即0racle调用层接口)是Oracle公司提供的由头文件和库函数等组成的一个访问Oracle数据库的应用程序编程接口(application programming interface API),它允许开发人员在第三代编程语言(包括C, C++, COBOL 与 FORTRAN)中通过SQL(Structure Query Language)来操纵Oracle数据库,而且OCI在一定程度上支持第三代编程语言(诸如C, C++, COBOL 与 FORTRAN)的数据类型、语法等等。OCI的显著特点是全面支持Oracle的面向对象技术,同时OCI还具有如下的一些特点:
1)非常有利于应用程序的设计;
2)高度控制应用程序的执行;
3)允许开发人员应用已熟悉的第三代程序设计语言来应用OCI;
4)支持动态SQL;
5)几乎所有的Oracle的开发工具都支持OCI;
6)通过回调技术(callbacks)来实现动态绑 定与定义;
7)通过OCI的描述函数可以获取Oracle数据库的各种参数;
8)增强了数组在DML(data manipulation language)语言中的应用;
OCI接口支持Windows NT和Windows 95/98/2000/XP操作系统,它所支持的C语言编译器包括Borland C++和MiroSoft VisualC++等。在使用0CI开发Oralce数据库应用程序之前,应首先安装这些操作系统和C语言编译工具。在选择安装OCI开发工具包后,Oracle安装程序将0CI文件拷贝到oracle主目录内的以下子目录中:
..BIN/:执行文件和帮助文件:
../OCIINCLUDE头文件;
..OCI/LIB/其中包含仍bc和\msvc两个子目录,分别用于存储支持Borland C++和MiroSoftVisualC++的OCI库文件,这些库文件与OCI源程序编译后所产生的目标文件进行链接生成可执行程序。一个应用OCI程序的生成可执行应用程序的过程如图1:
程序源代码 |
宿主语言编译器 |
目标代码 |
宿主语言链接器 |
OCI库 |
应用程序 |
Oracle Server |
图1
由此,我们可以看出:一个应用OCI的应用程序与其它不连接数据库的应用程序生成可执行程序的过程没有区别,在程序的链接阶段OCI库与源程序的目标代码文件链接而生成可执行程序。
2.OCI程序的基本结构
在一个应用程序中,我们是通过调用OCI提供的库函数来实现对Oracle数据库的操纵。OCI提供了上百个函数,都是以OCI开头的函数,比如创建OCI环境的OCI函数:OCIEnvCreate()。OCI函数的一个特点或者说是难点就是它的参数特别多,函数往往都有十几个参数。
一般情况下,一个OCI应用程序都是在多用户环境下的。在一个n层网络结构的配置中,客户端的应用程序需要完成一些数据操纵,包括交换数据与处理数据。一个OCI应用程序的基本结构包括:
1)初始化OCI环境和线程;
2)分配必要的句柄与数据结构;
3)建立与数据库的连接以及创建用户会话;
4)通过SQL与Oracle服务器交换数据,而后再做数据处理;
5)结束用户会话与断开与数据库的连接;
6)释放在程序中所分配的句柄。
示意如图2:
创建OCI环境(第一步) |
分配句柄与数据结构(第二步) |
连接数据库与开始会话(第三步) |
执行SQL与处理数据(第四步) |
结束会话与断开连接(第五步) |
释放句柄(第六步) |
图2 OCI程序的基本结构
3.在OCI应用程序中执行SQL的步骤
结构化查询语言(SQL Structure Query Language)是操纵关系数据库的主流语言,目前,几乎所有的商业数据库软件都支持SQL语言。标准的SQL语言按照它的功能不同,可以分为查询、操纵、定义以及控制四种类型。每一种都有若干关键字,具体如表1所示。
SQL功能 | 关键字 |
查询DQL (Data Query Language) | SELECT |
操纵DML (Data Manipulation Language) | INSERT,DELETE,UPDATE |
定义DDL (Data Define Language,) | CREATE,DROP |
控制DCL (Data Control Language) | GRANT,REVOKE |
表1
一个SQL语句在OCI应用程序中的执行步骤一般如下:
1)准备SQL语句。调用函数OCIStmtPrepare();
2)在SQL语句中绑定需要输入到SQL语句中的变量。对于DML语句来说,由于它带有输入变量,我们可以通过调用一个或者多个函数OCIBindByPos()、OCIBindByName()等把输入变量的地址绑定在DML语句中的占位符中;
3)执行SQL语句。调用OCIStmtExecute()函数。对于DDL语句到这一步就完成了一个语句的执行;
4)描述SQL中的输出的数据。如果有必要的话,我们可以调用函数OCIParamGet()与 OCIAttrGet()来获取我们所读取的记录的字段个数、字段的数据类型以及字段数据定义的最大长度。
5)定义输出变量。对于DQL(Data Query Language)语句,即SELECT的查询语句,需要定义一定数量的变量用来接受所选择列的数据。我们可以调用OCIDefineByPos()、OCIDefineObject()函数等来完成这个任务。也就建立SQL语句所返回的数据与应用程序中变量的关系。
6)获取数据。我们可以调用函数OCIStmtFetch()来把用SELECT选中的记录的数据赋予应用程序中的变量。
过程以及过程中调用到的函数如图3所示:
准备SQL |
OCIStmtPrepare() |
OCIBindByPos() OCIBindByName() OCIBindObject() OCIBindDynamic() OCIBindArrayOfStruct() |
执行 |
OCIStmtExecute() |
描述 |
定义变量 |
获取数据 |
OCIParamGet() OCIAttrGet() |
OCIDefineByPos(), OCIDefineObject() OCIDefineDynamic() OCIDefineArrayOfStruct() |
OCIStmtFetch() |
绑定 |
图3
虽然Oracle对标准的SQL语言有所扩展,但它也是建立在标准的SQL语言的基础之上。上图是一个一般SQL执行的流程图,对于不同的SQL语句,所需要的步骤也有所不同。对于DCL与DDL语句,由于没有数据的输入与输出,仅仅涉及到一些权限与定义或者删除数据库中的对象的问题,因此只需要上图的第一步与第三步便可以了。而对于DQL与DML语句,由于有数据的输入与输出,因此需要的步骤就多一些。其实,DML也可以只用两步来完成。这是因为DML语句中仅仅涉及数据的输入(即,数据从应用程序到数据库端),因此我们可以把所要输入的数据以字符串的形式放在SQL语句中。而DQL不仅可能有数据输入,而且也有数据输出(从数据库端到应用程序),因此,一个DQL语句需要如上图的六个步骤。
三、OCI在VC++6.0中的应用
第二部分是对OCI接口的简单介绍。下面我们就谈谈OCI在VC++中的应用。为了方便我们对应用OCI的理解,就以一个简单的MS VC++6.0的工程为例,把对OCI的解释放在这个工程之中。
1. 数据准备
首先,我们启动SQL *PLUS,然后我们可以以用户名SYSTEM,口令manager,主机字符串MYORACLE(其中MYORACLE是一个数据库实例的名称)登录到SQL Plus,用SQL语句来建立表:
1)创建表
CREATE TABLE t1
(ID NUMBER(5),
StudentID NUMBER(10),
Name VARCHAR2(12),
Old NUMBER(3),
Stature NUMBER(3,2));
表已创建。(执行CREATE后,有“表已创建。”,的提示,则说明我们创建表t1成功)。
2)添加数据
我们用INSERT语句来添加数据,如下:
INSERT INTO t1 VALUES
(1,20020101,'李华',23,1.77);
已创建 1 行。
用如此的格式加入几条记录,最后我们用“SELECT * FROM t1”来看一下表t1里的数据,如下:
ID STUDENTID NAME OLD STATURE
-- -------- ----- --- ------
1 20020101 李华 23 1.77
2 20020102 张万里 25 1.75
3 20020103 赵鹏成 27 1.84
4 20020104 高明就 28 1.85
5 20020105 王小明 55 1.99
执行COMMIT命令,以便保存数据到数据库端。
这样我们就在Oracle的数据库中建立了一个名称为t1的表;我们可以同样的方式建立另外一些表。
如果你不想自己建立表与输入数据,那么我们可以用Oracle数据库中自带的数据。首先,启动SQL *PLUS,然后我们可以以用户名SCOTT,口令tiger,主机字符串MYORACLE(你所建立的数据库实例的名称)登录SQL *PLUS;若登录成功,我们可以用“SELECT TABLE_NAME FROM USER_TABLES”命令来查看SCOTT用户的所有的表,一般情况下,我们可以看到SCOTT用户有诸如DEPT、EMP等等表。那么我们就可以利用这些数据来验证我们的应用程序了。若SQL *PLUS有“ERROR:ORA-01017: invalid username/password; logon denied”这样的提示,那么,说明数据库MYORACLE中没有SCOTT这个用户。我们可以在安装Oracle数据库的目录(比如你的Oracle数据库安装在C:上)C:/Oracle/Ora81/RDBMS/ADMIN下找到SCOTT.SQL文件,然后以system/manager(这个用户应该有创建用户与表的权限)登录到SQL *PLUS,执行命令:“@C://Oracle//Ora81//RDBMS//ADMIN//SCOTT.SQL;”,或者把SCOTT.SQL文件打开后把文件的内容copy并且在SQL *PLUS中执行,这样就建立了SCOTT用户以及他的一些表,比如表DEPT的数据如下:
DEPTNO DNAME LOC
------ ---------- -------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
2.建立一个工程
1) 打开VC++6.0,选择File/new,在Projects中选择建立的工程类型为MFC AppWizard(exe),然后我们键入工程名字,比如键入COCIExample,同时为工程选择一个路径,并且选择创建新工程。
2) 为了我们把注意力集中在OCI的应用上,我们建立一个基于对话框的应用程序。因此在MFC AppWizard的step 1中选择Dialog Based;在step 2到step 4中,我们都选择缺省值。这样就建立了一个基于对话框的应用程序。
3) 去掉对话框上的所有控件;
3.加入OCI的头文件与库文件
在安装Oracle数据库的时候,若选择了OCI选项,那么Oracle的主目录下就有OCI这样的目录(若你的Oracle数据库安装在C:的根目录下,则有“C:/Oracle/Ora81/oci”,具体在OCI概述中已经介绍)。
首先,加入OCI的一些头文件。在VC++6.0的主菜单上“Tools/Options”的对话框中的“Directories”选项下的“Include files”内容中加入OCI头文件所在的路径“C:/Oracle/Ora81/oci/include”,最后“Tools/Options”的“Include files”的内容如图4所示。同时在工程COCIExample的OCIExampleDlg.h文件中包含头文件oic.h。
图4
其次,加入OCI库文件。就是要加入“C:/Oracle/Ora81/oci/lib/msvc”目录下的文件oci.lib与ociw32.lib。在VC++6.0的主菜单上“Tools/Options”的对话框中的“Directories”选项下的“Libarary files”内容中加入OCI库文件所在的路径“C:/Oracle/Ora81/oci/lib/msvc”,最后“Tools/Options”的“Libarary files”的内容如图5所示。并且在VC的主菜单的“Project/Add to Project/Files”中把这两个OCI库文件加入工程中。
图5
加入OCI的头文件与库文件,也可以采用最简单的办法,即把上述的文件copy到我们所创建的工程的所在路径,OCI库文件oci.lib与ociw32.lib用“Project/Add to Project/Files”加入所创建的工程就可以了。
4.在工程COCIExample中应用OCI
如图2所示,在一个应用程序中应用OCI可以分为六个步骤,下面我们就以OCI程序的步骤为主线来介绍OCI在VC++6.0中的应用。
4.1创建OCI环境
首先,在本工程的COCIExampleDlg类中,加入public类型的一些必须的OCI句柄。如代码4.1.1
代码4.1.1:
public: OCIEnv *envhp;//环境句柄 OCIServer *srvhp;//服务器句柄 OCISvcCtx *svchp;//服务环境句柄 OCIError *errhp;//错误句柄 OCISession *authp;//会话句柄 OCIStmt *stmthp;//语句句柄 OCIDescribe *dschp;//描述句柄 其次,在本工程的对话框资源中加入一个Button控件(序号1,详细配置见表4,以下加入的控件的配置情况都在表4中), 同时利用ClassWizard为该按钮添加一个“BN_CLICKED”函数――OnButConnectdb(),在这个函数中我们实现OCI程序的前三步,即:创建OCI环境,分配句柄与数据结构,连接数据库与开始会话。并且列举数据库中某个用户的所有基表。 第三,我们在对话框中加入一个List Box控件(序号2),并且利用ClassWizard为该控件添加两个变量: m_listTablename(类型为ClistBox)与m_strTablename (类型为Cstring)。我们在该控件中显示所连接用户的所有基表(table base)。 我们在OnButConnectdb()中添加创建OCI环境的代码4.1.2。 //代码4.1.2,初始化OCI环境 OCIEnvInit(&envhp,OCI_DEFAULT,0, 0 ); 我们在类COCIExampleDlg中加入一个报告OCI错误的函数ErrorProc(),代码如下: //代码4.1.3 ErrorProc(dvoid *err, sword status) { Cstring str;sb4 errcode;text errbuf[512]; if(status==OCI_ERROR) { //调用OCI的错误获取函数 OCIErrorGet((dvoid*)errhp,(ub4)1,NULL, &errcode,errbuf, (ub4)sizeof(errbuf), OCI_HTYPE_ERROR); str.Format("错误号:%d/n错误信息:%s/n", errcode,errbuf); AfxMessageBox(str); }} 4.2分配句柄与数据结构 在函数OnButConnectdb()中加入如4.2.1代码来分配OCI的句柄与描述符。 //代码4.2.1:分配句柄与数据结构 //分配错误句柄 OCIHandleAlloc(envhp,(void**)&errhp, OCI_HTYPE_ERROR,0,0); //分配服务器句柄 OCIHandleAlloc(envhp,(void**)&srvhp, OCI_HTYPE_SERVER,0,0); //分配服务环境句柄 OCIHandleAlloc(envhp,(void**)&svchp, OCI_HTYPE_SVCCTX,0,0); //分配会话句柄 OCIHandleAlloc(envhp,(void **)&authp, OCI_HTYPE_SESSION,0,0); //分配描述句柄 OCIHandleAlloc((dvoid *) envhp, (dvoid **) &dschp, OCI_HTYPE_DESCRIBE,0,0); //分配语句句柄 OCIHandleAlloc((dvoid *) envhp, (dvoid**)&stmthp, OCI_HTYPE_STMT, 0, 0)); 说明:常量 “OCI_HTYPE_STMT”等等是OCI句柄的宏定义,标明我们所分配的句柄的类型。句柄的宏定义具体可以参考oci.h文件中的“Handle Types”部分。 4.3连接数据库与开始会话 由于在连接数据库与开始用户会话中需要数据库实例的名称、用户名称以及用户密码,因此我们在这个工程中加入一个对话框类来接收这些信息的输入。在OCIExample工程中加入基于CDialog类的对话框类――CCconnectDlg类。 加入CcconnectDlg类的步骤:首先,在VC的“WorkSpace/ResourceView”中选中“Dialog”,并且右击鼠标后,弹出上下文菜单,选中“Insert Dialog”,这样就给本工程中加入了一个对话框资源;其次,选中刚才加入的对话框,右击鼠标,选中上下文菜单中的“ClassWizard”,选择“Create new Class”,输入类的名称――CCconnectDlg;第三,在刚加入的对话框上(CcconnectDlg)加入如表2的控件。第四,给其中的三个CEdit控件加入用来接收用户输入信息的CString类型的变量,如表1。这样就在本工程中加入了一个可以接收输入用户信息的对话框类。
表2 接着我们在函数OnButConnectdb()中加入如4.3.1代码,来接收用户输入信息。 //代码4.3.1,生成接收用户输入数据的对话框 CConectDlg ConnDlg; ConnDlg.m_strDBName="MyOracleDB"; ConnDlg.m_strUserName="scott"; ConnDlg.m_strUserID="tiger"; if(ConnDlg.DoModal()==IDCANCEL)return; 下面我们就可以利用用户输入的登录信息来连接数据库并且开始会话。 在函数OnButConnectdb()中加入连接数据库代码。 //代码4.3.2,连接数据库 UpdateData(TRUE);sword status; status=OCIServerAttach(srvhp,errhp, (unsignedchar*)(LPCTSTR)ConnDlg.m_strDBName, (sb4)strlen(ConnDlg.m_strDBName),OCI_DEFAU; // 设置数据库的属性:服务器环境 ErrorProc(errhp,OCIAttrSet((dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX, (dvoid *) srvhp, (ub4) 0, OCI_ATTR_SERVER, errhp)); //代码4.3.3,认证用户并且开始会话 LPCTSTR uid, pwd; uid=ConnDlg.m_strUserName; pwd=ConnDlg.m_strUserID; //认证用户名称; ErrorProc(errhp,OCIAttrSet(authp, OCI_HTYPE_SESSION, (dvoid *)uid, (ub4)strlen(uid),OCI_ATTR_USERNAME,errhp)); //认证用户密码 ErrorProc(errhp,OCIAttrSet(authp, OCI_HTYPE_SESSION, (dvoid *)pwd, (ub4)strlen(pwd),OCI_ATTR_PASSWORD,errhp)); //以申明的用户名称与密码开始会话 status=OCISessionBegin(svchp, errhp, authp,OCI_CRED_RDBMS,OCI_DEFAULT); if(status!=0) return ; // 设置服务器环境 status=OCIAttrSet(svchp,OCI_HTYPE_SVCCTX, (dvoid*)authp,0,OCI_ATTR_SESSION,errhp); if(status!=0) return;
4.4执行SQL 这一部分比较如复杂。在OCI环境中执行SQL的过程在第二部分的3中已经分析过了。我们首先做一个没有条件的SELECT类型的SQL语句中OCI中的执行过程,而且按照第二部分的3中的步骤进行。在在函数OnButConnectdb()中加入如下几段代码,其目的就是把我们所连接的数据库的某个用户的所有基表都读取到应用程序中,并且把基表的名称显示到到我们在4.1中加入的ListBox控件中。 首先,要在本过程的类COCIExampleDlg中加入一些public类型的变量与OCI的句柄。如代码4.4.1 代码4.4.1 OCIDefine *defhp[20];//定义句柄 OCIBind *bidhp[20];//绑定句柄 OCIParam *colhp; //列句柄 ub2 collen[30]; //列长度 ub2 coltype[30];//列类型 //存放SELECT语句选中的列数据 text* colbuf[30]; sb2 ind[30];//指示符变量 我们注意到,这里有诸如ub2、text等变量类型,这是OCI的头文件oci.h中定义的一些适合OCI的C语言变量类型,其中常用类型与C语言的对应关系如表3:更详细的资料可以参考oci.h文件的宏定义部分。
表3 //代码4.4.2,处理SQL的第一步,准备SQL CString strSQL; text textSQL[1024]; strSQL="SELECT TABLE_NAME FROM USER_TABLES"; wsprintf((char*)textSQL,"%s",strSQL); status=OCIStmtPrepare(stmthp,errhp, textSQL,strlen((char*)textSQL), OCI_NTV_SYNTAX,OCI_DEFAULT ); 由于我们所要处理的这个SELECT语句没有选择条件,即SQL语句中没有输入数据。因此我们就不需要处理SQL的第二步“绑定”; //代码4.4.3,处理SQL的第三步:执行 ErrorProc(errhp,OCIStmtExecute(svchp, stmthp,errhp,(ub4)0,0, NULL,NULL, OCI_DEFAULT))
//代码4.4.4,处理SQL的第四步,描述 ub4 col_num;//存放SELECT语句选中的列数 //为选择项分配参数描述符 ErrorProc(errhp,OCIParamGet(stmthp, OCI_HTYPE_STMT,errhp, (void **)&colhp,1)); //读取选择项的数据长度 ErrorProc(errhp,OCIAttrGet(colhp, OCI_DTYPE_PARAM,&collen[0],0, OCI_ATTR_DATA_SIZE,errhp)); //读取选择项的数据类型 ErrorProc(errhp,OCIAttrGet(colhp, OCI_DTYPE_PARAM,&coltype[0],0, OCI_ATTR_DATA_TYPE,errhp)); //分配缓冲区 colbuf[0]=(text*)new text[(int)collen[0]+1]; //代码4.4.5处理SQL的第五步,定义变量 ErrorProc(errhp,OCIDefineByPos(stmthp, &defhp[k-1], errhp, 1, (ub1*)colbuf[0],collen[0]+1, SQLT_STR,&ind[0],0,0,OCI_DEFAULT)); //代码4.4.6,处理SQL的第六步,取值 while((OCIStmtFetch(stmthp,errhp,1, OCI_FETCH_NEXT,OCI_DEFAULT))!=OCI_NO_DATA) { //把获取的用户的基表的名称加到ListBox中m_listTablename.AddString((char*) colbuf[0]); } delete colbuf[0]; //删除所分配的缓冲区
4.5结束会话、断开连接 以及释放句柄 以上我们就已经做了一个OCI应用程序六步骤的前三步,即创建OCI环境、分配句柄与数据结构、连接数据库与开始会话以及执行SQL与处理数据,那么,一个完整的OCI应用程序还需要两个步骤,即结束会话与断开连接与释放句柄,我们可以把这两部分放在COCIExampleDlg类的析构函数中,或者类COCIExampleDlg的虚函数DestroyWindow()中,代码如下: //代码4.5.1, //结束会话 OCISessionEnd(svchp, errhp, authp, (ub4) 0); //断开与数据库的连接 OCIServerDetach(srvhp,errhp,OCI_DEFAULT); //释放OCI句柄 OCIHandleFree((dvoid*)srvhp, OCI_HTYPE_SERVER); OCIHandleFree((dvoid*)svchp, OCI_HTYPE_SVCCTX); OCIHandleFree((dvoid*)errhp, OCI_HTYPE_ERROR); OCIHandleFree((dvoid*)authp, OCI_HTYPE_SESSION); OCIHandleFree((dvoid*)stmthp, OCI_HTYPE_STMT); OCIHandleFree((dvoid*)dschp, OCI_HTYPE_DESCRIBE); 4.6小结 从4.1到4.4就是在一个应用程序中应用OCI的完整步骤。但是仅仅在4.3中演示了不带条件的DQL语句在OCI中的应用方法,还没有涉及到其它类型的SQL语句。因此,下面的内容是进一步演示各种类型的SQL语句在OCI中的实现过程。 5.在ListCtrl控件中显示表的数据 我们先在对话框上添加一个Button控件(序号3)与一个List Control(序号4)(具体配置见表),并且加入control类型的变量m_listCtrl。然后利用ClassWizard为Button控件添加一个“BN_CLICKED”函数――OnBTableselectok()。我们利用这个函数实现从控件2选中的表里读出字段名以及数据并且把它们显示到控件4上。同时,我们在类COCIExampleDlg中加入如下的public类型的变量: //代码5 //存储表的字段名称 CString ColName[50]; //存储表的字段名称 CString ColType[50];//存储字段的的数据类型 CString ColVal[50][100]; //存储表的字段值 int ColumnNumbers; //字段的数目 CString TableName; //所选中表的名称
在函数OnBTableselectok()中添加代码如下: //代码5.1,获取控件2所选中的表名 TableName=""; int item=m_listTablename.GetCurSel(); m_listTablename.GetText(item,TableName); if(TableName=="") return;
//代码5.2,处理SQL的第一步,准备SQL text textSQL[1024]; sword status; wsprintf((char*)textSQL, "SELECT * FROM %s",TableName); if(status=OCIStmtPrepare(stmthp,errhp, textSQL,strlen((char*)textSQL), OCI_NTV_SYNTAX,OCI_DEFAULT )) {ErrorProc(errhp,status); return;}
//代码5.3,处理SQL的第三步,执行 if(status=OCIStmtExecute(svchp,stmthp, errhp,(ub4)0,0,NULL,NULL,OCI_DEFAULT)) { ErrorProc(errhp,status); return; } //代码5.4,处理SQL的第四步,描述 ub4 col_num;//存放SELECT语句选中的列数 //代码5.4.1,读取选择列表中的项数 ErrorProc(errhp,OCIAttrGet(stmthp, OCI_HTYPE_STMT,&col_num,0, OCI_ATTR_PARAM_COUNT,errhp)); ColumnNumbers=(int) col_num; text *namep; //字段名称 ub4 sizep; //字段名称的字符串长度 text tempText[100]; //获取表的字段的属性信息 for(int i=0;i<(int)col_num;i++) { //为选择项分配参数描述符 ErrorProc(errhp,OCIParamGet(stmthp, OCI_HTYPE_STMT,errhp, (void **)&colhp,ub4(i+1))); //读取选择项的数据长度 ErrorProc(errhp,OCIAttrGet(colhp, OCI_DTYPE_PARAM,&collen[i],0, OCI_ATTR_DATA_SIZE,errhp)); //读取选择项的数据类型 ErrorProc(errhp,OCIAttrGet(colhp, OCI_DTYPE_PARAM,&coltype[i],0, OCI_ATTR_DATA_TYPE,errhp)); //若这个字段为日期型,则把其字符宽度置为30 if(coltype[i]==SQLT_DAT) collen[i]=30; //分配缓冲区 colbuf[i]=(text*)new text[(int)collen[i]+1]; //代码5.4.2获取字段名称 ErrorProc(errhp,OCIAttrGet(colhp, OCI_DTYPE_PARAM, (dvoid*)&namep, (ub4*)&sizep,OCI_ATTR_NAME, errhp)); strncpy((char *)tempText, (char *)namep, (size_t) sizep); tempText[sizep] = '/0'; //把字段的名称赋予字段名称数组 ColName[i].Format("%s",tempText); } //代码5.5,处理SQL的第五步,定义变量 for(i=0;i<(int)col_num;i++) { if(status=OCIDefineByPos(stmthp,&defhp[i], errhp,i+1,(ub1*)colbuf[i], collen[i]+1, SQLT_STR,&ind[i],0,0, OCI_DEFAULT)) { ErrorProc(errhp,status); return; } } //代码5.6,处理SQL的第六步,取值 int row=0; while((OCIStmtFetch(stmthp,errhp,1,OCI_FETCH_NEXT,OCI_DEFAULT))!=OCI_NO_DATA) { for(i=0;i<(int)col_num;i++) {//把获取的用户的基表的数据 //赋予字段数值数组 ColVal[row][i]=colbuf[i]; } row=row+1; } //删除所分配的缓冲区 for(i=0;i<(int)col_num;i++)delete colbuf[i];
//代码5.7,把获取的数据显示到ListCtrl上 //设置ListCtrl的样式 m_listCtrl.ModifyStyle(NULL, LVS_REPORT); m_listCtrl.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT |LVS_EX_GRIDLINES); m_listCtrl.SetFocus(); //加ListCtrl数据列的标题 int ColNumber= m_listCtrl.GetHeaderCtrl()->GetItemCount(); //清空 for(i=0;i<ColNumber;i++) m_listCtrl.DeleteColumn(0);
|
//加列的标题
for(i=0;i<(int)col_num;i++)
m_listCtrl.InsertColumn(i,ColName[i],
LVCFMT_LEFT,60,0);
//加ListCtrl的内容
m_listCtrl.DeleteAllItems();
for(i=0;i<row;i++)
{
m_listCtrl.InsertItem(LVIF_TEXT|LVIF_STATE,
i, ColVal[i][0],
(i%2)==0 ? LVIS_SELECTED : 0, LVIS_SELECTED, 0, 0);
for(int j=1;j<(int)col_num;j++)
m_listCtrl.SetItemText(i,j, ColVal[i][j]);
}
这里也是处理一个不带条件的SELECT语句,但是与4.3有所不同.首先,这个SQL语句在运行前并不知道我们要查询的表的字段个数与字段的数据类型,因此用代码5.4.1中用OCIAttrGet()函数从语句句柄stmthp中获取SELECT语句中选择的字段的数目;其次,在这里我们不仅需要字段的数据,而且还需要字段的名称,因此,我们用代码5.4.2来获取字段名称。
6.简单查询-带有条件的DQL语句
为了演示带有条件的SELECT语句在OCI中的执行过程,我们设计一个简单的查询。
首先,我们加入一个Button控件(序号5), 并且通过MFC ClassWizard为此控件添加一个“BN_CLICKED”函数――OnButQuery(),用它来执行查询命令;第二,我们加入一个Combo box控件(序号6),利用ClassWizard为这个Combo box控件加入一个Cstring类型变量m_strFieldName,用它来选择所查询的字段名称;第三,我们加入一个Combo Box(序号7),利用ClassWizard为这个Combo Box控件加入一个Cstring型的变量m_strcondition, 用它来选择查询的条件,可以选择“>”, “<”, “=”(其中,这三个符号是我们在Combo Box的属性对话框的Data项里,手工输入的);第四,我们加入一个CEdit控件(序号8),并且加入Cstring类型的变量m_strFieldVal,用它来接收所查询字段的数值。
我们在5所加的函数OnBTableselectok()中加入代码6.1,用来把我们所选择的表的字段添加到用来选择查询字段名称的ComboBox控件上,
//代码6.1
//初始化选择所查询字段的ComboBox控件
for(i=0;i<(int)col_num;i++)
m_comboCtrl.AddString(ColName[i]);
//代码6.2,获取查询条件
UpdateData(TRUE);
CString FieldName=m_strFieldName;
CString strCondition=m_strcondition;
CString strFieldVal=m_strFieldVal;
//代码6.3,处理SQL的第一步,准备SQL语句
sword status; text textSQL[1024];
wsprintf((char*)textSQL,"SELECT * FROM
%s WHERE%s %s :CONDITION",
TableName,FieldName,strCondition);
if(status=OCIStmtPrepare(stmthp,errhp,
textSQL,strlen((char*)textSQL),
OCI_NTV_SYNTAX,OCI_DEFAULT ))
{ ErrorProc(errhp,status); return; }
//代码6.4,处理SQL的第二步,绑定(binding)
text *textConValp;//条件字段变量
sb4 len;
int strlength=strFieldVal.GetLength();
textConValp=new text[strlength+1];
wsprintf((char*)textConValp,"%s",
strFieldVal);
//使得条件字段变量为以空字符结尾的字符串
textConValp[strlength]='/0';
//条件字段的长度(字符个数)
len=strlen((const char *)textConValp)+1;
//由名称来绑定
if(status=OCIBindByName(stmthp, &bidhp[0],
errhp,(text*) ":CONDITION",-1,
(ub1 *) textConValp, len, SQLT_STR,
0,0,0,0,0,OCI_DEFAULT))
{ ErrorProc(errhp,status); return; }
//代码6.5,处理SQL的第三步,执行
if(status=OCIStmtExecute(svchp,stmthp,
errhp,(ub4)0,0,NULL,NULL,OCI_DEFAULT))
{ ErrorProc(errhp,status); return; }
//代码6.6,处理SQL的第四步,描述
ub4 col_num;//存放SELECT语句选中的列数
//读取选择列表中的项数
ErrorProc(errhp,OCIAttrGet(stmthp,
OCI_HTYPE_STMT,&col_num,0,
OCI_ATTR_PARAM_COUNT,errhp));
for(int i=0;i<(int)col_num;i++)
{
//为选择项分配参数描述符
ErrorProc(errhp,OCIParamGet(stmthp,
OCI_HTYPE_STMT,errhp,
(void **)&colhp,ub4(i+1)));
//读取选择项的数据长度
ErrorProc(errhp,OCIAttrGet(colhp,
OCI_DTYPE_PARAM,&collen[i],0,
OCI_ATTR_DATA_SIZE,errhp));
//读取选择项的数据类型
ErrorProc(errhp,OCIAttrGet(colhp,
OCI_DTYPE_PARAM,&coltype[i],
0,OCI_ATTR_DATA_TYPE,errhp));
//若这个字段为日期型,则把其字符宽度置为30
if(coltype[i]==SQLT_DAT) collen[i]=30;
//分配缓冲区
colbuf[i]=(text*)newtext[(int)collen[i]+1];
}
//代码6.7,处理SQL的第五步,定义变量
for( i=0;i<(int)col_num;i++)
{
if(status=OCIDefineByPos(stmthp,
&defhp[i],errhp,i+1,(ub1*)colbuf[i],
collen[i]+1,SQLT_STR,&ind[i],
0,0,OCI_DEFAULT))
{ ErrorProc(errhp,status); return; }
}
我们在程序中并没有调用OCIHandleAlloc()函数来分配定义句柄(OCIDefine),其实,定义句柄是由OCIDefineByPos()函数自动分配的。这样的句柄还有环境句柄(OCIEnv)与绑定句柄(OCIBind)。
//代码6.8,处理SQL的第六步,取值
int row=0;
while((OCIStmtFetch(stmthp,errhp,1,
OCI_FETCH_NEXT,OCI_DEFAULT))!=OCI_NO_DATA)
{
for(i=0;i<(int)col_num;i++)
{//把获取的用户的基表的数据
//赋予字段数值数组
ColVal[row][i]="";//清空字段值数组
if(ind[i]==-1)//对空值的处理
ColVal[row][i]="";
else
ColVal[row][i]=colbuf[i];
}
row=row+1;
}
//删除所分配的缓冲区
for(i=0;i<(int)col_num;i++)delete colbuf[i];
//代码6.9,刷新ListCtrl控件
m_listCtrl.DeleteAllItems();
for(i=0;i<row;i++)
{ m_listCtrl.InsertItem(
LVIF_TEXT|LVIF_STATE, i,
ColVal[i][0],
(i%2)==0?LVIS_SELECTED:0,
LVIS_SELECTED, 0, 0);
for(int j=1;j<(int)col_num;j++)
m_listCtrl.SetItemText(i,j, ColVal[i][j]);
}
delete textConValp;
这里要说明的是代码6.4,即OCI处理SQL语句的第二步:绑定(binding)。OCI有按照位置绑定函数:OCIBindByPos ()与按照名称绑定函数OCIBindByName()。 我们在程序中应用了后者。OCI的绑定中有一个概念叫Placeholder――占位符。对于在SQL语句中有输入数数据的SQL,为了实现交互式程序,我们在设计程序阶段并不能确定所要输入的数据,因此我们在这个阶段就用占位符来表示输入数据,例如在代码6.4我们用指针变量textConValp来表示输入数据(textConValp在SQL语句中的位置由":CONDITION"来表示)。而在程序运行阶段,则将占位符与程序变量地址结合,执行语句时,变量的数值就赋予这个SQL语句。从而实现交互式SQL。
带有输入的SQL语句的执行还有另外一种方式,即把输入数据以字符串的形式置于SQL语句中,比如:CString strSQL;
srtSQL.Format(“SELECT * FROM tablename WHERE fieldname=%s”,strInputVal)。这种方式仅仅需要两步:准备SQL与执行SQL,若是UPDATE或者INSERT则在执行成功后调用提交以便保持数据到数据库端。不过,我们需要判读输入数据的类型,比如数字型与字符型输入数据就有区别,若是字符型则需要在输入数据两边加单引号,熟悉SQL*PLUS的用户都清楚这一点。
7.删除记录――DELETE语句的实现
为实现这个功能我们在对话框中加入一个Cbutton控件(序号9),为它加一个“BN_CLICKED”函数――OnButDelete()来执行删除命令。我们在该函数中加入如下代码:
//代码
7.1,处理SQL的第一步,准备SQL语句
UpdateData(TRUE); sword status;
text textSQL[1024];
wsprintf((char*)textSQL,"DELETE FROM %s WHERE
%s%s :DelFieldVal”,
TableName,m_strFieldName,m_strcondition);
if(status=OCIStmtPrepare(stmthp,errhp,
textSQL,strlen((char*)textSQL),
OCI_NTV_SYNTAX,OCI_DEFAULT ))
{ ErrorProc(errhp,status); return; }
//代码
7.2,处理SQL的第二步,绑定(binding)
text textColVal[20];//条件字段的数值
sb4 len;
int strlength=m_strFieldVal.GetLength();
wsprintf((char*)textColVal,”%s”,
m_strFieldVal);
textColVal[strlength]=’/0’;
len=strlen((const char *)textColVal)+1;
//由名称来绑定
if(status=OCIBindByName(stmthp, &bidhp[0],
errhp, (text *) “:DelFieldVal”,-1,
(ub1 *) &textColVal, len, SQLT_STR,
0,(ub2 *)0,(ub2*)0,(ub4) 0,(ub4 *) 0, OCI_DEFAULT))
{ ErrorProc(errhp,status); return; }
//代码
7.3,处理SQL的第三步,执行
if(status=OCIStmtExecute(svchp,stmthp,
errhp,(ub4)1,0,NULL,NULL,OCI_DEFAULT))
{ErrorProc(errhp,status);return;}
//刷新ListCtrl控件
OnBTableselectok();
这个SQL语句的特点是:仅仅有输入数据而没有返回数据,因此我们处理这个SQL语句仅用来三步。
8.修改记录-UPDATE语句的实现
要实现这个功能,我们需要让程序知道我们想修改哪条记录的哪个字段以及这个字段的新值。因此,我们加入一些控件来输入这些信息。我们在类COCIExample的对话框中首先加入一个ComboBox控件(序号10),并给其加入Cstring类型的变量m_strConFName与Control类型的变量m_comConFNameCtrl;其次,加入一个CEdit控件(序号11),并且加入Cstring类型的变量m_strConFVal;用它们来确定所要修改的记录。其次,加入Cbutton控件(序号12),为它加一个“BN_CLICKED”函数――OnButUpdate()来执行修改命令。
首先,我们在函数OnButConnectdb()(在4.1中说明)中加入初始化刚才加入的ComboBox,代码如下:
//代码
8.1初始化修改字段时的ComboBox控件
for(I=0;I<(int)col_num;I++) m_comConFNameCtrl.AddString(ColName[I]);
我们在函数OnButUpdate()中加入如下代码:
//代码
8.2处理SQL的第一步,准备SQL语句
UpdateData(TRUE); sword status;
text textSQL[1024];
wsprintf((char*)textSQL,"UPDATE %s SET %s = :UpDFieldVal WHERE %s=:KeyFieldVal", TableName,m_strFieldName,m_strConFName);
if(status=OCIStmtPrepare(stmthp,errhp,
textSQL,strlen((cha*)textSQL),
OCI_NTV_SYNTAX,OCI_DEFAULT ))
{ ErrorProc(errhp,status);return;}
//代码
8.3处理SQL的第二步,绑定(binding)
text textColVal[20];//所修改字段的数值
sb4 len;
int strlength=m_strFieldVal.GetLength();
wsprintf((char*)textColVal,"%s",
m_strFieldVal);
textColVal[strlength]='/0';
len=strlen((const char *)textColVal)+1;
//由名称来绑定所要修改的字段的占位符
if(status=OCIBindByName(stmthp, &bidhp[0], errhp, (text*) ":UpDFieldVal",-1,
(ub1 *) &textColVal, len, SQLT_STR,
0, 0, 0, 0, 0, OCI_DEFAULT))
{ ErrorProc(errhp,status);return;}
//由名称来绑定作为修改时条件的字段的占位符
text textConColVal[20];
strlength=m_strConFVal.GetLength();
wsprintf((char*)textConColVal,"%s",
m_strConFVal);
textConColVal[strlength]='/0';
len=strlen((const char *)textConColVal)+1;
if(status=OCIBindByName(stmthp, &bidhp[1], errhp, (text *) ":KeyFieldVal",-1,
(ub1 *) &textConColVal, len, SQLT_STR,
0, 0, 0, 0, 0, OCI_DEFAULT))
{ErrorProc(errhp,status);return;}
//代码
8.4处理SQL的第三步,执行
if(status=OCIStmtExecute(svchp,stmthp,
errhp,(ub4)1,0,NULL,NULL,OCI_DEFAULT))
{ErrorProc(errhp,status);return;}
//提交
status=OCITransCommit(svchp, errhp, (ub4)0);
//刷新ListCtrl控件
OnBTableselectok();
需要说明的是UPDATE成功后,还需要调用函数OCITransCommit(),以便把所修改的数据保持到数据库中。
9.增加记录-INSERT语句
在OCI中处理INSERT语句所需的步骤与UPDATE和DELETE语句相同,即需要准备SQL语句、绑定(binding)、执行,若执行成功则调用OCITransCommit()提交(commit)来保持所增加的记录到数据库端。由于一方面,演示灵活的INSERT语句需要很多笔墨,另一方面,若你正学习OCI编程,那么,不妨把这作为一个练习(笔者在写这篇文章时,已经实现了对结构不同的表的增加记录)。
四、总结
通过上面的学习,我们不仅学习了OCI接口的优点、程序结构与处理SQL语句的步骤,而且学习了在VC++中OCI处理各种SQL语句的方法。
当然,这只是一个初步的了解,若想用OCI来开发项目,则需要进一步地学习。如果上面还有不明白的地方,或者想交流OCI编程的经验与技巧,那么请与我联系(Email:mister_6090@sina.com)。http://www.comprg.com.cn/list.asp?nsort_id=148