精华内容
下载资源
问答
  • OCI

    2012-01-17 05:08:03
     与OCI7.3中使用的宿主语言定义变量存储空间(很拗口,不用管它)的方式不同,OCI9以后已不再使用原有的变量结构来初始化及维护数据库的信息,而改用句柄的形式来和Oracle数据库进行交互。将常用的句柄定义在一个结构...
    OCI 
    
    2011年12月05日
      [b]OCI编程的一般过程[/b]
      与OCI7.3中使用的宿主语言定义变量存储空间(很拗口,不用管它)的方式不同,OCI9以后已不再使用原有的变量结构来初始化及维护数据库的信息,而改用句柄的形式来和Oracle数据库进行交互。将常用的句柄定义在一个结构内,方便程序维护:
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      typedef struct _OCI_HANDLE
      {
      OCIEnv *phEnv; //环境句柄,要使用oracle数据库,必须首先获得环境句柄
      OCISvcCtx *phService; //oracle的服务句柄,也可以说是连接句柄。
      OCIError *phErr; //oracle的错误句柄,可以获取错误信息
      OCIStmt *phStmt; //oracle的语句描述句柄
      OCIServer *phServer; //Oracle的服务器句柄
      OCISession *phSession; //Oracle会话句柄
      }OCIHANDLE, *LPOCIHANDLE;
      OCI9编程的一般步骤有:初始化环境句柄、生成其他各类句柄、建立数据库连接进行登录、执行SQL语句,对返回的结果进行处理、终止用户会话,断开连接,释放各种句柄。
      
      
       上图给出OCI初始化的一个过程,OCI能初始化成功的前提当然是系统中已经安装或设置了Oracle的client端(在本文第四节有介绍)。其中步骤(3)到(7)都分别调用OCIHandleAlloc()函数进行分配,顺序可以不同,它们都只依赖环境句柄;步骤(1)和(2)可以使用OCIEnvCreate()函数替换掉,这两种的初始化OCI环境的方法在不同的使用条件下是不同的,一般建议使用OCIEnvCreate()代替OCIInitialize()和OCIEnvInit(),因为OCIInitialize()和OCIEnvInit()主要是为了backwards-compatible。而如果是编写DLL更是应该使用OCIEnvCreate()函数,user‘guide是这样说的:
      If you are writing a DLL or a shared library using OCI library then this call should definitely be used instead of OCIInitialize() and OCIEnvInit() call.
      OCI各句柄初始化完毕后,接下来就是连接数据库,如下图:
      
      
      
      数据库连接好后可以执行SQL语句:一条SQL语句在OCI应用程序中的执行步骤一般如下:(1)准备SQL语句。(2)在SQL语句中绑定需要输入到SQL语句中的变量。(3)执行SQL语句。(4)获取SQL中的输出描述。(5)定义输出变量。(6)获取数据。具体过程及过程中调用的函数如下图所示。对于SQL中的定义语句(如CREATE,DROP)和控制语句(如GRANT,REVOKE),由于没有数据的输入输出,只需要图2中第一步和第三步即可。操作语句(如INSERT,DELETE,UPDATE)则需要执行前三步。而查询语句(如SELECT)不仅可能有数据输入,而且也有数据的输出,因此需要执行六个步骤。
      
      
       OCI编程的一般过程还是很清晰的,流程图都是一条线………
      [b]三、单次查询返回多行结果的实现[/b]
      设计的时候老大要求要像原有接口库那样一次查询返回多行,然后再在本地进行处理,以减少对数据库的访问。这本来是一个很正常的要求,但后面看了好些开源的OCI封装,发现它们的demo里都没有给出如何fetch多行……… 比如写的比较好的ocilib,demo中就没有给出(至少是以前的版本没给出,现在就不知道了),在写完这个OCI接口库大概半年后,再看ocilib的代码OCIDefineByPos()函数时,发现倒数第三第四个参数都是指针,说明可以fetch多行。进而发现ocilib可以通过OCI_SetFetchSize()函数来设置查询返回的行数。但为什么不在demo里给一个示例呢?就连该版本的文档里也没有这个函数的说明。很奇怪!没办法还是自己动手丰衣足食,使劲啃user’guide。
      需要fetch多行,首先要考虑执行select语句后,接收到数据放在什么地方?当然是放在缓冲区里了,在OCI里通过不同的变量函数绑定来告诉oracle client把从数据库取到的数据存放在什么地方。这里使用OCIDefineByPos()函数。下面以每次取100行为例给出具体步骤:
      1. 分配足够大的缓冲区m_pData = new unsigned char[m_DataLen * 100]。m_DataLen表示数据库表中每一行的长度(各个列长度之和),这样需要使用unsigned作Buffer,因为如果使用有符号char则取带时间的表会有问题。
      2. 根据各列的长度来定义各列在缓冲区中的位置:
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      OCIDefineByPos(m_hOCI.phStmt,        //语句句柄
      &(m_vecColInfo.phdefine),//定义句柄
      m_hOCI.phErr,   
      i+1,              //列序号
      (ub1 *)(&(m_pData[pos])), //各列的位置pos等于当前列之前各列长度和乘以100
      m_vecColInfo.collen,   //对应列的长度 SQLT_STR,
      (m_vecColInfo.indp),   //指示器,因为每次最多要取100行,所以indp应设为维数为100的数组。
      (m_vecColInfo.rlenp),  //返回数据的真实长度,这里也应把rlenp设为维数100的数组
      0,
      OCI_DEFAULT));
      m_vecColInfo为保存各列信息的vector。值得注意的是OCIDefineByPos()的第八和第九个参数:第八个参数是指示器参数,在OCIStmtFectch后只是所取的对应数据是否完整(0表示完整),由于要取100行,则在m_vecColInfo中每一个列元素对应的结构中都应定义indp[100]的数组。第九个参数用于返回所取数据的实际长度,因此也需要在一个列元素的结构体中定义rlenp[100]的数组。还有一个需要注意的是第四个参数里的pos,pos用于指定该列保存在Buffer中的起始位置。如下表是数据库中某表,执行select查询该表前100行后,数据在缓冲区m_pData中保存数据的形式如图4:
      CarKey
      MakeKey
      ModelKey
      ColorKey
      Year
      1
      1
      1
      2
      2003
      2
      2
      1
      3
      2005
      3
      2
      1
      2
      2005
      …….
      …….
      ……..
      ……..
      ………
      100
      2
      1
      1
      2006
      
      
       上图可以看到OCI在fetch多行时,先将第一列的100行数据放入m_pData中,然后以列为单位每次取100行放入m_pData。因此pos变量的赋值应写为:pos += 100 * (m_vecColInfo[i-1].collen); 其中collen代表该列的长度。
      3. 获取数据:
      ?
      1
      2
      3
      4
      5
      OCIStmtFetch(m_hOCI.phStmt,
        m_hOCI.phErr,
        100,//每次取100行的数据
        OCI_FETCH_NEXT,
        OCI_DEFAULT);
      第三个参数设置为100后,执行OCIStmtFetch完毕后数据就填充到缓冲区中。这里需要注意的是最后一个fetch,因为最后一次fetch时数据库表中往往已经不足100行,所以每次执行OCIStmtFetch()函数完毕要需要检查其返回值,当返回值为OCI_NO_DATA时使用:
      ?
      1
      2
      3
      4
      5
      6
      OCIAttrGet(m_hOCI.phStmt,
      OCI_HTYPE_STMT,
      (dvoid *) &row_fetched,
      (ub4 *) NULL,
      (ub4) OCI_ATTR_ROWS_FETCHED,
      m_hOCI.phErr);
      row_fetched将返回剩下的行数,倒数第二个参数为OCI_ATTR_ROWS_FETCHED,在oci.h中是这样定义的:
      #define OCI_ATTR_ROWS_FETCHED 197 /* rows fetched in last call */
      在oci10中这里没有任何问题,但在一些较早的oci9版本中找不到OCI_ATTR_ROWS_FETCHED的定义……… 也就是说无法fetch多行?!迫不得已只能用oci10。可能ocilib在demo中没有fetch多行的示例也是出于这个考虑吧。
      [b]四、Oracle即时客户端(instantclient)的配置[/b]
      过去使用OCI需要安装oracle的客户端,Oracle的普通客户端一般都很庞大,Windows平台下的客户端就有700M。Oracle公司在10g版本后推出了大小只有30M的InstantClient(即时客户端)作为oracle的访问客户端。不需要安装就可以访问Oracle的服务器。
      [b]Windows平台下instantclient的配置和使用:[/b]
      下面以C:\Oracle为例介绍具体的配置过程。
      1.将instantclient的basic包及sqlplus包中所有文件解压至C:\Oracle。
      2.配置系统的环境变量:
      • 将 C:\Oracle 添加到 PATH 中(位于其他 Oracle 目录之前)。例如,在 Windows 2000 上,依次单击“开始”->“设置”->“控制面板”->“系统”->“高级”->“环境变量”,编辑系统变量列表中的 PATH。WindowXP上,右击“我的电脑”->“高级”->“环境变量”。
      • 添加用户环境变量 TNS_ADMIN 设置为C:\Oracle。
      • 设置必要的 Oracle 全球化语言环境变量, 添加用户环境变量NLS_LANG 中文对应的字符集是 SIMPLIFIED CHINESE_CHINA.ZHS16GBK
      3. 一共设置以下三个环境变量(以解压缩目录C:\Oracle为例)环境变量名 变量值
      path C:\Oracle
      TNS_ADMIN C:\Oracle
      ORACLE_HOME C:\Oracle (可选)
      NLS_LANG SIMPLIFIED CHINESE_CHINA.ZHS16GBK
      4. tnsnames.ora和sqlnet.ora文件,这两个文件可以在所要访问的Oracle数据库服务器的$ORACLE_HOME/network/admin目录下找到,把tnsnames.ora中的服务器主机名改为ip地址即可。需更改时注意备份原来的文件。
      5. 配置完毕后进入C:\Oracle运行sqlplus.exe登陆对应的数据库测试是否设置正确。在windows下使用instantclient时,需要将instantclient的sdk包中的include和lib加到工程中。
      [b]Unix平台下instantclient的配置和使用:[/b]
      本例中使用solaris_x86_10.2.0.2为客户端
      1. 将instantclient_solaris_x86_10.2.0.2中的basic、sqlplus和sdk解压至同一目录,用chmod将该目录下的所有文件设为可读写,比如:chmod
    展开全文
  • Oracle OCIOCI说明

    2013-05-27 19:36:18
    Oracle oci工具包安装:  $ORACLE_HOME\BIN:执行文件和help文件  $ORACLE_HOME\OCI\INCLUDE:头文件  $ORACLE_HOME\OCI\LIB\BC: for Borlanf C++的OCI库  $ORACLE_HOME\OCI\LIB\MSVC: for MS Visual C++...
     Oracle oci工具包安装:
    

        $ORACLE_HOME\BIN:执行文件和help文件
        $ORACLE_HOME\OCI\INCLUDE:头文件
        $ORACLE_HOME\OCI\LIB\BC:  for Borlanf C++的OCI库
        $ORACLE_HOME\OCI\LIB\MSVC:  for MS Visual C++的OCI库

    如果是unix下,对于ORACLE8i,则OCI库在$ORACLE_HOME/lib下,如果是9i,则在$ORACLE_HOME/lib32下,库文件名一般为libclntsh.so

    1. 创建OCI环境即创建和初始化OCI工作环境,其他的OCI函数需要OCI环境才能执行。

    2. 需要申请的句柄类型:

        OCI环境句柄: OCI_HTYPE_ENV—它定义所有OCI函数的环境调用环境,是其他句柄的父句柄。(由OCIEnvInit或OCIEnvCreate生成)

        错误句柄:OCI_HTYPE_ERROR—作为一些OCI函数的参数,用来记录这些OCI函数操作过程中所产生的错误,当有错误发生时,可用COIErrorGet()来读取错误句柄 中记录的错误信息。
        服务器环境句柄:OCI_HTYPE_SVCCTX—定义OCI调用的服务器操作环境,它包含服务器、用户会话和事务三种句柄。
        服务器句柄:OCI_HTYPE_SERVER—标识数据源,它转换为与服务器的物理连接。
        用户会话句柄:OCI_HTYPE_SESSION—定义用户角色和权限及OCI调用的执行环境。
        事务句柄:OCI_HTYPE_TRANS—定义执行SQL操作的事务环境,事务环境中包含用户的会话状态信息。
        语句句柄:OCI_HTYPE_STMT—是一个标识SQL语句或PL/SQL块,以及其相关属性的环境。
        Bind/Define句柄:属于语句句柄的子句柄,由OCI库隐式自动生成。用户不需要自己再申请,OCI输入变量存储在bind 句柄中,输出变量存储在定义句柄中


     3. 句柄属性包括:

        服务器环境句柄属性:(OCI_HTYPE_SVCCTX)

             OCI_ATTR_SERVER—设置/读取服务环境的服务器环境属性
             OCI_ATTR_SESSION—设置/读取服务环境的会话认证环境属性
             OCI_ATTR_TRANS—设置/读取服务环境的事务环境属性

        用户会话句柄属性:(OCI_HTYPE_SESSION)

             OCI_ATTR_USERNAME—设置会话认证所使用的用户名
             OCI_ATTR_PASSWORD—设置会话认证所使用的用户口令

        服务器句柄:(OCI_HTYPE_SEVER)

            OCI_ATTR_NOBLOCKING_MODE—设置/读取服务器连接:=TRUE时服务器连接设置为非阻塞方式

        语句句柄:(OCI_HTYPE_STMT)

            OCI_ATTR_ROW_COUNT—只读,为当前已处理的行数,其default=1
            OCI_ATTR_STMT_TYPE—读取当前SQL语句的类型:
            Eg : OCI_STMT_BEGIN
                    OCI_STMT_SELECT   OCI_STMT_INSERT    
                    OCI_STMT_UPDATE  OCI_STMT_DELETE
                    OCI_ATTR_PARAM_COUNT—返回语句选择列表中的列数

    4. 关于输出变量定义:如果在语句执行前就知道select语句的选择列表结构,则定义输出操作可在调用 OCISTMTExecute前进行,如果查询语句的参数为用户动态输入的,则必须在执行后定义。
    5. OCI函数返回值:

        OCI_SUCCESS –函数执行成功 (=0)
        OCI_SUCCESS_WITH_INFO –执行成功,但有诊断消息返回,可能是警告信息
        OCI_NO_DATA—函数执行完成,但没有其他数据
        OCI_ERROR—函数执行错误
        OCI_INVALID_HANDLE—传递给函数的参数为无效句柄,或传回的句柄无效
        OCI_NEED_DATA—需要应用程序提供运行时刻的数据
        OCI_CONTINUE—回调函数返回代码,说明回调函数需要OCI库恢复其正常的处理操作
        OCI_STILL_EXECUTING—服务环境建立在非阻塞模式,OCI函数调用正在执行中。

    6. OCI连接有二种方式:

            Blocking(阻塞方式)和non_Blocking(非阻塞方式),阻塞方式就是当调用 OCI操作时,必须等到此OCI操作完成后服务器才返回客户端相应的信息,不管是成功还是失败。非阻塞方式是当客户端提交OCI操作给服务器后,服务器立即返回OCI_STILL_EXECUTING信息,而并不等待服务端的操作完成。

            对于non-blocking方式,应用程序若收到一个OCI函数的返回值为 OCI_STILL_EXECUTING时必须再次对每一个OCI函数的返回值进行判断,判断其成功与否。

            可通过设置服务器属性为OCI_ATTR_NONBLOCKING_MODE来实现。系统默认方式为阻塞模式.

    7. OCI函数设置的模式有:

          OCI_DEFUALT:使用OCI默认的环境
          OCI_THREADED:线程环境下使用OCI
          OCI_OBJECT:对象模式
          OCI_SHARED:共享模式
          OCI_EVENTS
          OCI_NO_UCB
          OCI_ENV_NO_MUTEX:非互斥访问模式

      其中模式可以用逻辑运算符进行迭加,将函数设置成多多种模式:如mode=OCI_SHREADED| OCI_OBJECT

    8. 当应用进程与服务器断开连接时,程序没有使用OCITransCommit()进行事务的提交,则所有活动的事务会自动回滚。
    9. OCI重定义数据类型
    typedef      unsigned char      ub1;    
    typedef     signed char       sb1;
    typedef     unsigned short   ub2;  
    typedef     signed short     sb2;
    typedef     unsigned int       ub4;   
    typedef     signed int       sb4;  
    typedef     ub4               duword;    
    typedef     sb4               dsword;    
    typedef     dsword            dword; 

    10. 在SQL语句准备后,可以用OCIAttrSet(0设置该语句的类型属性OCI_ATTR_STMT_TYPE,以后可读取语句属性,根据属性分别进行处理。

    11. 批量绑定输入和定义输出参数:将数据存入一个静态数据组中。一次执行可以提交或读取多行记录值。

    12. 结合占位符和指示器变量:

            占位符:在程序中,一些SQL语句需要在程序运行时才能确定它的语句数据,在设计时可用一个占位符来代替,当程序运行时,在它准备好语句后,必须为每个占位符指定一个变量,即将占位符与程序变量地址结合,执行时,Oracle就从这些变量中读取数据,并将它们与SQL语句一起传递给Oracle服务器执行。OCI结合占位符时,它将占位符与程序变量关联起来,并同时要指出程序变量的数据类型和数据长度。

        如:select * from test where name=:p1 and age>:p2  :p1和:p2为占位符


            指示器变量:由于在Oracle中,列值可以为NULL,但在C语言中没有NULL值,为了能使OCI程序表达NULL列值,OCI函数允许程序为所执行语句中的结合变量同时关联一个指示符变量或指示符变量数组,以说明所结合的占位符是否为NULL或所读取的列值是否为NULL,以及所读取的列值是否被截取。
    除SQLT_NTY(SQL Named DataType)外,指示符变量或指示符变量数组的数据类型为sb2,其值说明:

        作为输入变量时:(如insert ,update语句中) =-1:OCI程序将NULL赋给Oracle表的列,忽略占位符结合的程序变量值 >=0:应用程序将程序变量值赋给指定列

        作为输出变量时:(如select语句中) =-2:所读取的列数据长度大于程序变量的长度,则被截取。 =-1:所读取的值为NULL,输出变量的值不会被改变。 =0:数据被完整读入到指定的程序变量中

        >0:所读取的列数据长度大于程序变量的长度,则被截取,指示符变量值为所读取数据被截取前的实际长度

    三. OCI函数说明
    注:红色为输入参数   蓝色为输出参数  ,否则为输入/出参数
    //示例以下面结构作为说明
    sword         swResult;
    OCIBind        *hBind;
    OCIDefine    *hDefine;
    OCIStmt        *stmtp
    OCIError    *errhp;
    OCIStmt     *stmtp
    OCISvcCtx      *svchp
    OCIEnv         *envhpp;
    OCISession *usrhp;
    sb2         sb2aInd[30]; //指示器变量,用于取可能存在空值的字段
    
    Typedef strcut
    {
        char tname[40];
        int age;
    } t_std;
    
    typedef struct
    {
        sb2 sb2_tname[100];
        sb2 sb2_age[100];
    } stdInd_T;  //指示器数组
    
    typedef struct
    {
       ub2 ub2_tname[100];
       ub2 ub2_age[100];
    } stdLen_T; //字段长度
    
    t_std tstd[100];  //数组变量,用于批量操作
    stdInd_T tstdInd;
    stdLen_T tstdLen;
    stdLen_T tstdRet;
    
    t_std std;

    各函数数明

    1.创建OCI环境
    sword OCIEnvCreate(  
        OCIEnv **envhpp,  //OCI环境句柄指针
        ub4 mode, //初始化模式:OCI_DEFAULT/OCI_THREADED 等
        CONST dvoid *ctxp,
        CONST dvoid *(*malicfp)(dvoid *ctxp,size_t size),
        CONST dvoid *(ralocfp)(dvoid *ctxp,dvoid *memptr,size_t newsize),
        CONST void *(*mfreefp)(dvoid *ctxp,dvoid *memptr),
        Size_t xstramemsz,
        Dvoid **usrmempp
    )
    
    eg :
        swResult = OCIEnvCreate(&envhpp, OCI_DEFAULT, NULL, NULL, NULL, NULL, 0, NULL);
        if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO)
            return FALSE;
    
    sword OCIInitialize (
        ub4           mode,
        CONST dvoid   *ctxp,
        CONST dvoid   *(*malocfp)   (/* dvoid *ctxp,  size_t size _*/),
        CONST dvoid   *(*ralocfp) (/*_ dvoid *ctxp, dvoid *memptr, size_t newsize _*/),
        CONST void    (*mfreefp) (/*_ dvoid *ctxp, dvoid *memptr _*/)
    );
    
    sword OCIEnvInit (
        OCIEnv    **envhpp,
        ub4       mode,
        size_t    xtramemsz,
        dvoid     **usrmempp
     );
     
    //注:在8i以后,可用OCIEnvCreate一个函数就可以初始化环境了,相当于OCIInitialize+ OCIEnvInit

    2.申请/释放句柄
    sword OCIHandleAlloc(
        CONST dvoid *parenth,  //新申请句柄的父句柄,一般为OCI环境句柄
        Dvoid **hndlpp,   //申请的新句柄
        Ub4 type, type,  //句柄类型
        Size_t xtramem_sz,   //申请的内存数
        Dvoid **usrmempp  //申请到的内存块指针
    )
    //注:一般需要申请的句柄有:
    
        服务器句柄OCIServer, 句柄类型OCI_HTYPE_SERVER
        错误句柄OCIError,用于捕获OCI错误信息, 句柄类型OCI_HTYPE_ERROR
        事务句柄OCISession, 句柄类型OCI_HTYPE_SESSION
        上下文句柄OCISvcCtx, 句柄类型OCI_HTYPE_SVCCTX
        SQL语句句柄OCIStmt, 句柄类型OCI_HTYPE_STMT
    
        eg: 申请一个错误句柄OCIError
            swResult = OCIHandleAlloc(envhpp, (dvoid *)& errhp, OCI_HTYPE_ERROR, 0, NULL);
            if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO)
            {
                return FALSE;
            }
    
    
    释放句柄
    
    sword OCIHandleFree(
        dvoid *hndlp,  //要释放的句柄
        ub4 type   //句柄类型
    )
    
        eg:
            OCIHandleFree(stmtp, OCI_HTYPE_STMT)
    

    3.读取/设置句柄属性
    sword OCIAttrSet(
        dvoid *trgthndlp,  //需设置的句柄名
        ub4  trghndltyp, //句柄类型
        dvoid *attributep, //设置的属性名
        ub4 size, //属性值长度
        ub4 attrtype,     //属性类型
        OCIError *errhp   //错误句柄
    )
    
    sword OCIAttrGet(
        dvoid *trgthndlp,  //需读取的句柄名
        ub4  trghndltyp, //句柄类型
        dvoid *attributep, //读取的属性名
        ub4 *sizep, //属性值长度
        ub4 attrtype,     //属性类型
        OCIError *errhp   //错误句柄
    )
    //注:一般要设置的属性有:
        服务器实例:
            句柄类型OCI_HTYPE_SVCCTX,属性类型OCI_ATTR_SERVER
        连接数据的用户名:
            句柄类型OCI_HTYPE_SESSION,属性类型OCI_ATTR_USERNAME
        用户密码
            句柄类型OCI_HTYPE_SESSION,属性类型OCI_ATTR_PASSWORD
        事务:  
            句柄类型OCI_HTYPE_SVCCTX,属性类型OCI_ATTR_SESSION
    
        eg:设置用户名和密码
            char username[20],passwd[20];
            strcpy(username,”tiger”)
            strcpy(passwd,”cotton”)
            swResult = OCIAttrSet(usrhp, OCI_HTYPE_SESSION,(text*) username, strlen(username), OCI_ATTR_USERNAME, errhp);
            if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO)
                return FALSE;
    
            swResult = OCIAttrSet(usrhp, OCI_HTYPE_SESSION,    (text*) passwd, strlen(passwd), OCI_ATTR_PASSWORD, errhp);
            if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO)
                return FALSE;

    4.连接/断开服务器
     
    //多用户方式连接:
    sword  OCIServerAttach(
        OCIServer     *srvhp,//未初始化的服务器句柄
        OCIError      *errhp,
        CONST text    *dblink,//服务器SID
        sb4           dblink_len,
        ub4           mode //=OCI_DEFAULT,系统环境将设为阻塞方式
    );
    
    sword OCIServerDetach (
        OCIServer   *srvhp,
        OCIError    *errhp,
        ub4         mode //OCI_DEFAULT
    );
    //单用户方式连接:
    sword OCILogon (
        OCIEnv          *envhp,
        OCIError        *errhp,
        OCISvcCtx       **svchp,
        CONST text      *username,
        ub4             uname_len,
        CONST text      *password,
        ub4             passwd_len,
        CONST text      *dbname,
        ub4             dbname_len
    );
    
    sword OCILogoff (
        OCISvcCtx      *svchp
        OCIError       *errhp
    );

    5.开始/结束一个会话
    //先认证用户再建立一个会话连接
    sword OCISessionBegin (
    OCISvcCtx     *svchp,  //服务环境句柄
    OCIError      *errhp,
    OCISession    *usrhp,   //用户会话句柄
    ub4           credt,    //认证类型
    ub4           mode   //操作模式
    );
    
    sword OCISessionEnd (
    OCISvcCtx       *svchp,
    OCIError        *errhp,
    OCISession      *usrhp,
    ub4             mode );
    
    //认证类型:
        OCI_CRED_RDBMS:用数据库用户名和密码进行认证,则先要设置OCI_ATTR_USERNAME和OCI_ATTR_PASSWORD属性
        OCI_CRED_EXT:外部认证,不需要设置用户和密码
        OCI_DEFAULT:用户会话环境只能被指定的服务器环境句柄所设置
        OCI_SYSDBA:用户要具有sysdba权限
        OCI_SYSOPER:用户要具有sysoper权限
    
    
        eg:
            swResult = OCISessionBegin(svchp, errh,usrhp, OCI_CRED_RDBMS, OCI_DEFAULT);
            if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO)
                return FALSE;
    

    6.读取错误信息
    sword OCIErrorGet (
    dvoid      *hndlp, //错误句柄
    ub4        recordno,//从那里读取错误记录,从1开始
    text       *sqlstate,//已取消,=NULL
    sb4        *errcodep, //错误号
    text       *bufp,  //错误内容
    ub4        bufsiz,  //bufp长度
    ub4        type //传递的错误句柄类型 : =OCI_HTYPE_ERROR:错误句柄 : =OCI_HTYPE_ENV:环境句柄
    );
        eg:
            ub4    ub4RecordNo = 1;
            OCIError* hError
            sb4    sb4ErrorCode;
            char   sErrorMsg[1024];
    
            if (OCIErrorGet(hError, ub4RecordNo++, NULL, &sb4ErrorCode, (OraText*) sErrorMsg, sizeof(sErrorMsg), OCI_HTYPE_ERROR) == OCI_SUCCESS)
                printf(“error msg:%s\n”, sErrorMsg);

    7.准备SQL语句
    sword OCIStmtPrepare (
        OCIStmt       *stmtp,//语句句柄  
        OCIError      *errhp,
        CONST text    *stmt,  //SQL语句
        ub4           stmt_len,   //语句长度
        ub4           language,  //语句的语法格式=OCI_NTV_SYNTAX
        ub4           mode //=OCI_DEFAULT
    );
    
        eg:
            char sSQL[1024];
            
            sprintf(sSQL, “select table_name from user_tables”);
            swResult = OCIStmtPrepare(stmtp errhp,  (CONST OraText*)sSQL, strlen(sSQL), OCI_NTV_SYNTAX, OCI_DEFAULT);
            
            if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO)
                return FALSE;

    8. 绑定输入参数

    OCIBindArrayOfStruct():Set skip parameters for static array bind ,数组绑定,一般用于批量操作
    OCIBindByName():Bind by name  按名绑定
    OCIBindByPos():Bind by position  按位置绑定,建议一般按此方式绑定
    OCIBindDynamic():Sets additional attributes after bind with OCI_DATA_AT_EXEC mode  
    OCIBindObject():Set additional attributes for bind of named data type

    //注:OCIBindArrayOfStruct必须先用OCIBindByPos初始化,然后在OCIBindArrayOfStruct中定义每个参数所跳过的字节数
    sword OCIBindByName (
        OCIStmt       *stmtp, //语句句柄
        OCIBind       **bindpp,//结合句柄,=NULL
        OCIError      *errhp,
        CONST text    *placeholder,//占位符名称
        sb4           placeh_len, //占位符长度
        dvoid         *valuep, //绑定的变量名
        sb4           value_sz, //绑定的变量名长度
        ub2           dty,  //绑定的类型
        dvoid         *indp, //指示符变量指针(sb2类型),单条绑定时为NULL,
        ub2           *alenp, //说明执行前后被结合的数组变量中各元素数据实际的长度,单条绑定时为NULL
        ub2           *rcodep,//列级返回码数据指针,单条绑定时为NULL
        ub4           maxarr_len, //最多的记录数,如果是单条绑定,则为0
        ub4           *curelep, //实际的记录数,单条绑定则为NULL
        ub4           mode //=OCI_DEFAULT
    );
    
    sword OCIBindByPos (
        OCIStmt      *stmtp,
        OCIBind      **bindpp,
        OCIError     *errhp,
        ub4          position,// 绑定的位置
        dvoid        *valuep,
        sb4          value_sz,
        ub2          dty,
        dvoid        *indp,
        ub2          *alenp,
        ub2          *rcodep,
        ub4          maxarr_len,
        ub4          *curelep,
        ub4          mode
    );
    
    sword OCIBindArrayOfStruct (
        OCIBind     *bindp,//绑定的结构句柄,由OCIBindByPos定义
        OCIError    *errhp,
        ub4         pvskip, //下一列跳过的字节数**
        ub4         indskip,//下一个指示器或数组跳过的字节数
        ub4         alskip, //下一个实际值跳过的字节数
        ub4         rcskip //下一个列级返回值跳过的字节数
    );
    
        eg:
            sword     swResult;
            OCIBind*  hBind;
            Ub4 rec_num;
            //Sql:  insert into student values (:p1,:p2)
    
            //单条绑定:
            hBind = NULL;
            swResult = OCIBindByPos(stmtp &hBind, errhp,1,ststd.tname,
            sizeof(ststd.tname), SQLT_CHR, NULL,
            NULL,NULL,0, NULL, OCI_DEFAULT);
    
            //批量取数据,一次取100条
            //Sql:  select username,age from student where username=:p1 and age=:p2
    
            hBind = NULL;
            swResult = OCIBindByPos(stmtp &hBind, errhp,1,tstd[0].tname,
            sizeof(tstd[0].tname), SQLT_CHR, &tstdInd.sb2_usernmae[0],&tstdLen.ub2_username[0],&tstdRet.ub2_username[0],100, &rec_num, OCI_DEFAULT);
            swResult = OCIBindArrayOfStruct(hBind, errhp,sizeof(tstd [0]), sizeof(sb2), sizeof(ub2), sizeof(ub2));

    9.执行SQL语句

    sword OCIStmtExecute (
        OCISvcCtx           *svchp,  //服务环境句柄
        OCIStmt             *stmtp,  //语句句柄
        OCIError            *errhp,
        ub4                 iters,
        ub4                 rowoff,
        CONST OCISnapshot   *snap_in,
        OCISnapshot         *snap_out,
        ub4                 mode
    );

        //注:
        1.iters:对于select语句,它说明一次执行读取到buffer中的记录行数,如果不能确定select语句所返回的行数,可将iters设置为0,而对于其他的语句,iters表示这些语句的执行次数,此时iters不能为0。
        2.rowoff:在多行执行时,该参数表示从所结合的数据变量中的第几条记录开始执行(即记录偏移量)。
        3.mode:
            =OCI_DEFAULT:default模式
            =OCI_DESCRIBE_ONLY:描述模式,只返回选择列表的描述信息,而不执行语句
            =OCI_COMMIT_ON_SUCCESS:自动提交模式,当执行成功后,自动提交。
            =OCI_EXACT_FETCH:精确提取模式。
            =OCI_BATCH_ERRORS:批错误执行模式:用于执行数组方式的操作,在此模式下,批量insert,update,delete时,执行过程中任何一条记录错误不会导致整个insert,update,delete失败,系统自动会收集错误信息,而在非批错误方式下,其中的任何一条记录错误,将会导致整个操作失败。
        Eg:
            //执行一次
            swResult = OCIStmtExecute(svchp, stmtp,  errhp, 1, 0, NULL, NULL, OCI_DEFAULT);
    
            //批量执行100次:
            swResult = OCIStmtExecute(svchp, stmtp,  errhp, 100, 0, NULL, NULL, OCI_DEFAULT);

    10.定义输出变量
     
    OCIDefineArrayOfStruct():Set additional attributes for static array define  
    OCIDefineByPos():Define an output variable association  
    OCIDefineDynamic():Sets additional attributes for define in OCI_DYNAMIC_FETCH mode  
    OCIDefineObject():Set additional attributes for define of named data type  

    sword OCIDefineByPos (
        OCIStmt     *stmtp, //语句句柄
        OCIDefine   **defnpp,//定义句柄—用于数组变量
        OCIError    *errhp,
        ub4         position,//位置序号(从1 开始)
        dvoid       *valuep, //输出的变量名
        sb4         value_sz, //变量长度
        ub2         dty,  //数据类型
        dvoid       *indp, //指示器变量/指示器变量数组,如果此字段可能存在空值,则要指示器变量,否则单条处理时为NULL
        ub2         *rlenp, //提取的数据长度
        ub2         *rcodep, //列级返回码数组指针
        ub4         mode //OCI_DEFAULT
    );
    
    sword OCIDefineArrayOfStruct (
        OCIDefine   *defnp,//由OCIDefineByPos定义的句柄
        OCIError    *errhp,
        ub4         pvskip, //下一列跳过的字节数,一般就是结构的大小
        ub4         indskip,//下一个指示器或结构跳过的字节数,=0
        ub4         rlskip, //下一个实际值跳过的字节数,=0
        ub4         rcskip //下一个列列级返回值跳过的字节数,=0
    );
    
    sword OCIDefineDynamic (
        OCIDefine   *defnp,
        OCIError    *errhp,
        dvoid       *octxp,
        OCICallbackDefine(ocbfp)(/*_
            dvoid          *octxp,
            OCIDefine      *defnp,
            ub4            iter,
            dvoid          **bufpp,
            ub4            **alenpp,
            ub1            *piecep,
            dvoid          **indpp,
            ub2            **rcodep _*/
        )  
    );
    
    sword OCIDefineObject (
        OCIDefine       *defnp,
        OCIError        *errhp,
        CONST OCIType   *type,
        dvoid           **pgvpp,
        ub4             *pvszsp,
        dvoid           **indpp,
        ub4             *indszp
    );
    
        eg:
            //单条查询
            //sql: select username,age from student  where username=:p1;
            
            //如果此字段有可能有空值,则
            hDefine = NULL;
            swResult = OCIDefineByPos(stmtp &hDefine, errhp, 1, tstd.username, sizeof(tstd.username), SQLT_CHR, & sb2aInd[0], NULL, NULL, OCI_DEFAULT);
            
            //如果此字段没有空值,则
            hDefine = NULL;
            swResult = OCIDefineByPos(stmtp &hDefine, errhp, 1, tstd.username, sizeof(tstd.username), SQLT_CHR, NULL, NULL, NULL, OCI_DEFAULT);
    
             //批量查询
            //select username,age from student  where age>30;
            hDefine = NULL;
            swResult = OCIDefineByPos(stmtp, &hDefine, errhp, 1, &tstd[0].username,
            sizeof(tstd[0].usenmae), SQLT_CHR, NULL, NULL, NULL, OCI_DEFAULT);
            swResult = OCIDefineArrayOfStruct(hDefine, errhp, sizeof(tstd[0]), 0, 0, 0);

    11.提取结果
    sword OCIStmtFetch (
        OCIStmt     *stmtp,//语句句柄
        OCIError    *errhp,
        ub4         nrows, //从当前位置处开始一次提取的记录数,对于数据变量,可以>1,否则不能>1
        ub2         orientation,//提取的方向:OCI_FETCH_NEXT
        ub4         mode //OCI_DEFAULT
    )
    
        eg:
            while ((swResult=OCIStmtFetch stmtp errhp,1,OCI_FETCH_NEXT,OCI_DEFAULT)) != OCI_NO_DATA)
            {
                ……
            }

    12.事务操作
    //开始一个事务
    sword OCITransStart (
        OCISvcCtx    *svchp,
        OCIError     *errhp,
        uword        timeout,
        ub4          flags
    );
    
    //注:
        1. Timeout:
            当flag=OCI_TRANS_RESUME:它表示还有多少秒事务将被激活
            当flag=OCI_TRANS_NEW: 事务响应的超时时间(秒)
        2. Flags:指定一个新的事务还是已有事务
            =OCI_TRANS_NEW:定义一个新的事务
            =OCI_TRANS_RESUME
    
        //准备一个事务:
        sword OCITransPrepare (
            OCISvcCtx    *svchp,
            OCIError     *errhp,
            ub4          flags //OCI_DEFAULT
        );
    
        sword OCITransForget (
            OCISvcCtx     *svchp,
            OCIError      *errhp,
            ub4           flags //OCI_DEFAULT
        );
    
        //断开一个事务:
        sword OCITransDetach (
            OCISvcCtx    *svchp,
            OCIError     *errhp,
            ub4          flags //OCI_DEFAULT
        );
    
        //提交一个事务:
        sword OCITransCommit (
            OCISvcCtx    *svchp,  //服务环境句柄
            OCIError     *errhp,
            ub4          flags //OCI_DEFAULT
        );
         
        //回滚一个事务
        sword OCITransRollback (
            dvoid        *svchp,
            OCIError     *errhp,
            ub4          flags //OCI_DEFAULT
        ); 

    四. OCI数据类型与C语言数据类型对照表

       表字段类型                     OCI类型                   C类型                       备注
        Number(N)                              SQLT_UIN                     unsigned int                     无符号整型
        Number(N)                              SQLT_INT                     int                              有符号整型
        Number(n,m)                            SQLT_FLT                     float                            符点数
        Varchar2(N)                            Sqlt_chr                    char                             字符串
        Raw(N)                               Sqlt_BIN                      具体看不同的定义                 二进制类型,多用于一个结构字段
        DATE                                   SQLT_DAT                      无                               最好转换成字符串或数字


    展开全文
  • $oci_sql = "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA' NLS_DATE_LANGUAGE='AMERICAN'"; $stid = oci_parse($oci_conn, $oci_sql); $r = oci_...

    $oci_sql = "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA' NLS_DATE_LANGUAGE='AMERICAN'";
    $stid = oci_parse($oci_conn, $oci_sql);
    $r = oci_execute($stid);

    展开全文
  • OCI编程

    2013-11-28 10:08:46
    最近公司做的一个项目,要处理海量数据,数据是存放在Oracle数据库里,刚开始用的是ADO访问,速度极慢,后来改用Proc,效果还是不如人意,最后才用的OCI。因为之前对OCI不了解过,经2个星期的努力,终于完成了对...

    最近公司做的一个项目,要处理海量数据,数据是存放在Oracle数据库里,刚开始用的是ADO访问,速度极慢,后来改用Proc,效果还是不如人意,最后才用的OCI。因为之前对OCI不了解过,经2个星期的努力,终于完成了对Oracle数据库中的海量数据的读取与插入,速度的确很快,测试读取七千五百万条数据连一分钟都不到。

    之前用Proc,没用OCI,是因为Proc容易学,OCI相对难学些,说起难学,主要就是因为Oracle提供的一百多个API函数中每个函数都至少带有七八个参数,而每个参数都具有不同的重要的意义,一不小心设置错了,可能会导致潜在的错误,调试的时候也很难发现,这个我深有体会啊!

    下面结合我的学习过程,讲一下OCI编程:

    1:环境的配置

    一:系统环境:要想使用OCI编程需要安装Oracle的客户端,而这个普通的客户端比较大,在Oracle10g版本后推出了大小只有30M的Instantclient(即时客户端)作为Oracle的访问客户端。

    具体的配置可以参考这里:http://www.cnblogs.com/ychellboy/archive/2010/04/16/1713884.html

    二:执行环境:

    windows下的配置可以参考这里:http://blog.csdn.net/sherlockhua/article/details/4353531

    linux下的配置可以参考这里:http://blog.csdn.net/sherlockhua/article/details/4353531                 

    2:基本理论

    ㈠首先要创建OCI 环境即创建和初始化OCI 工作环境,其他的OCI 函数需要OCI 环境才能执行。

    ㈡分配OCI 环境句柄:它定义所有OCI 函数的调用环境,是其他句柄的父句柄。( 由OCIEnvInit 或OCIEnvCreate 生成 ) 。

    ㈢错误句柄:作为一些OCI 函数的参数,用来记录这些OCI 函数操作过程中所产生的错误,当有错误发生时,可用OCIErrorGet() 来读取错误句柄中记录的错误信息。

    ㈣服务器环境句柄:定义OCI 调用的服务器操作环境,它包含服务器、用户会话和事务三种句柄。

    ㈤服务器句柄:标识数据源,它转换为与服务器的物理连接。

    ㈥用户会话句柄:定义用户角色和权限及OCI 调用的执行环境。

    ㈦事务句柄:定义执行 SQL 操作的事务环境,事务环境中包含用户的会话状态信息。

    ㈧语句句柄:是一个标识 SQL 语句或 PL/SQL 块,以及其相关属性的环境。

    ㈨Bind/Define句柄:属于语句句柄的子句柄,由OCI库隐式自动生成。用户不需要自己再申请,OCI输入变量存储在bind 句柄中,输出变量存储在定义句柄中。

    注意:Bind/Define 句柄在执行具体的 SQL 语句的时候,被隐含创建并连接到表达句柄( Statement Handle )上,当表达句柄释放时,它们也被隐含释放。所以在执行每一个 sql 语句时,先分配表达句柄,执行结束后,释放表达句柄,这样做保证不发生由于定位句柄和绑定变量句柄引起的内存泄漏。

    3:连接Oracle数据库的步骤

    OCI 连接过程比较复杂,除了分配设置各个基本句柄外,还要明确彼此之间的联系,大致流程如下:

    创建环境句柄: OCIEnvCreate(&envhp, …);

    创建一个指定环境的错误句柄: OCIHandleAlloc((dvoid *)envhp, (dvoid **)&errhp,…);

    创建一个指定环境的服务器句柄: OCIHandleAlloc((dvoid *)envhp, (dvoid **)&servhp,…);

    建立到数据源的访问路径 : OCIServerAttach(servhpp, errhpp,…);

    创建一个指定环境的服务上下文句柄: (void) OCIHandleAlloc((dvoid *)envhpp,…);

    为指定的句柄及描述符设置特定的属性: (void) OCIAttrSet((dvoid *)svchpp,…);

    创建一个指定环境的用户连接句柄: (void) OCIHandleAlloc((dvoid *)envhpp,…);

    为用户连接句柄设置登录名及密码: (void) OCIAttrSet((dvoid *)usrhpp,…);

    认证用户建立一个会话连接: OCISessionBegin(svchpp, errhpp,…);

    创建一个句子句柄: OCIHandleAlloc((dvoid *)envhpp,…);s

    准备 SQL 语句: OCIStmtPrepare(stmthpp, errhpp,…);

    绑定输入变量: OCIBindByPos(stmtp &hBind, errhp,…);

    绑定输出变量: OCIDefineByPos(stmthpp, &bhp1, errhpp,…);

    获得 SQL 语句类型: OCIAttrGet ((dvoid *)stmthpp, (ub4)OCI_HTYPE_STMT,…);

    执行 SQL 语句: OCIStmtExecute(svchpp, stmthpp,…);

    释放一个会话: OCISessionEnd();

    删除到数据源的访问 : OCIServerDetach(servhpp, errhpp, OCI_DEFAULT);

    释放句柄: OCIHandleFree((dvoid *) stmthpp, OCI_HTYPE_STMT);

    4:常用句柄属性

    服务器环境句柄属性: (OCI_HTYPE_SVCCTX)

    OCI_ATTR_SERVER— 设置 / 读取服务环境的服务器环境属性

    OCI_ATTR_SESSION— 设置 / 读取服务环境的会话认证环境属性

    OCI_ATTR_TRANS— 设置 / 读取服务环境的事务环境属性

    用户会话句柄属性: (OCI_HTYPE_SESSION)

    OCI_ATTR_USERNAME— 设置会话认证所使用的用户名

    OCI_ATTR_PASSWORD— 设置会话认证所使用的用户口令

    服务器句柄: (OCI_HTYPE_SEVER)

    OCI_ATTR_NOBLOCKING_MODE— 设置 / 读取服务器连接: =TRUE 时服务器连接设置为非阻塞方式

    语句句柄: (OCI_HTYPE_STMT)

    OCI_ATTR_ROWS_RETCHED   取得获取数据记录数

    OCI_ATTR_ROW_COUNT— 只读,为当前已处理的行数,其 default=1

    OCI_ATTR_STMT_TYPE— 读取当前 SQL 语句的类型: 
    Eg : OCI_STMT_BEGIN 
           OCI_STMT_SELECT   OCI_STMT_INSERT     
           OCI_STMT_UPDATE  OCI_STMT_DELETE 
           OCI_ATTR_PARAM_COUNT—返回语句选择列表中的列数 

    5:介绍一下OCI的常用函数,这些常用函数基本可以帮助你完成大多数的操作

    一:创建OCI环境 
    sword OCIEnvCreate(   
    OCIEnv **envhpp,  //OCI环境句柄指针 
    ub4 mode, //初始化模式:OCI_DEFAULT | OCI_THREADED  | OCI_OBJECT等
    CONST dvoid *ctxp,   
    CONST dvoid *(*malicfp)(dvoid *ctxp,size_t size), 
    CONST dvoid *(ralocfp)(dvoid *ctxp,dvoid *memptr,size_t newsize), 
    CONST void *(*mfreefp)(dvoid *ctxp,dvoid *memptr), 
    Size_t xstramemsz, 
    Dvoid **usrmempp 

    eg:创建OCI环境
    int nresult=OCIEnvCreate(&envhpp, OCI_DEFAULT, NULL, NULL, NULL, NULL, 0, NULL); 

    二:申请/释放句柄 
    sword OCIHandleAlloc( 
    CONST dvoid *parenth,  //新申请句柄的父句柄,一般为OCI环境句柄 
    Dvoid **hndlpp,   //申请的新句柄  
    Ub4 type, type,  //句柄类型 
    Size_t xtramem_sz,   //申请的内存数 
    Dvoid **usrmempp  //申请到的内存块指针 

    注:   一般需要申请的句柄有: 
    服务器句柄OCIServer, 句柄类型OCI_HTYPE_SERVER 
    错误句柄OCIError,用于捕获OCI错误信息, 句柄类型OCI_HTYPE_ERROR 
    上下文句柄OCISvcCtx, 句柄类型OCI_HTYPE_SVCCTX 
    事务句柄OCISession, 句柄类型OCI_HTYPE_SESSION 
    SQL语句句柄OCIStmt, 句柄类型OCI_HTYPE_STMT 


    eg: 申请一个错误句柄OCIError 
    swResult = OCIHandleAlloc(envhpp, (dvoid *)& errhp, OCI_HTYPE_ERROR, 0, NULL); 
    if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO) 
    {  return FALSE;  } 
       
    释放句柄 
    sword OCIHandleFree( 
    dvoid *hndlp,  //要释放的句柄 
    ub4 type   //句柄类型 

      
    eg: 释放语句句柄
    OCIHandleFree(stmtp, OCI_HTYPE_STMT) 

    三:读取/设置句柄属性

    sword OCIAttrSet( 
    dvoid *trgthndlp,  //需设置的句柄名 
    ub4  trghndltyp, //句柄类型 
    dvoid *attributep, //设置的属性名 
    ub4 size, //属性值长度 
    ub4 attrtype,     //属性类型 
    OCIError *errhp   //错误句柄 

    注:一般要设置的属性有: 
    服务器实例: 
    句柄类型OCI_HTYPE_SVCCTX,属性类型 OCI_ATTR_SERVER 
    连接数据库的用户名: 
    句柄类型OCI_HTYPE_SESSION,属性类型 OCI_ATTR_USERNAME 
    连接数据库用户密码:
    句柄类型OCI_HTYPE_SESSION,属性类型 OCI_ATTR_PASSWORD 
    事务:   
    句柄类型OCI_HTYPE_SVCCTX,属性类型 OCI_ATTR_SESSION 


    eg:设置用户名和密码 
    char username[20],passwd[20]; 
    strcpy(username,”tiger”) 
    strcpy(passwd,”cotton”) 
    swResult = OCIAttrSet(usrhp, OCI_HTYPE_SESSION,  (text*) username, strlen(username),  
    OCI_ATTR_USERNAME, errhp); 
    if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO) 
    return FALSE; 
    swResult = OCIAttrSet(usrhp, OCI_HTYPE_SESSION,    (text*) passwd, strlen(passwd),  
    OCI_ATTR_PASSWORD, errhp); 
    if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO) 
    return FALSE; 
       
    sword OCIAttrGet( 
    dvoid *trgthndlp,  // 需读取的句柄名 
    ub4  trghndltyp, //句柄类型 
    dvoid *attributep, //读取的属性名 
    ub4 *sizep, //属性值长度 
    ub4 attrtype,     //属性类型 
    OCIError *errhp   //错误句柄 


    四:连接/断开服务器 
    多用户方式连接: 
    sword  OCIServerAttach( 
    OCIServer     *srvhp,// 未初始化的服务器句柄 
    OCIError      *errhp, 
    CONST text    *dblink,//服务器SID 
    sb4           dblink_len, 
    ub4           mode //=OCI_DEFAULT, 系统环境将设为阻塞方式 
    ); 


    多用户方式断开:

    sword OCIServerDetach ( 
    OCIServer   *srvhp, 
    OCIError    *errhp, 
    ub4         mode //OCI_DEFAULT 
    );

     

    五:开始/结束一个会话 
    sword OCISessionBegin (  
    OCISvcCtx     *svchp,  //服务 环境句柄 
    OCIError      *errhp, 
    OCISession    *usrhp,   //用户会话句柄 
    ub4           credt,    //认证类型 
    ub4           mode   //操作模式 
    ); 
    认证类型: 
    OCI_CRED_RDBMS: 用数据库用户名和密码进行认证,则先要设置OCI_ATTR_USERNAME和OCI_ATTR_PASSWORD属性 
    OCI_CRED_EXT: 外部认证,不需要设置用户和密码 
    OCI_DEFAULT:用户会话环境只能被指定的服务器环境句柄所设置 
    OCI_SYSDBA:用户 要具有sysdba权限 
    OCI_SYSOPER:用户要具有sysoper权限 


    sword OCISessionEnd (  
    OCISvcCtx       *svchp, 
    OCIError        *errhp, 
    OCISession      *usrhp, 
    ub4             mode ); 


    六:读取错误信息 
    sword OCIErrorGet ( 
    dvoid      *hndlp, // 错误句柄 
    ub4        recordno,//从那里读取错误记录,从1开始 
    text       *sqlstate,//已取消,=NULL 
    sb4        *errcodep, //错误号 
    text       *bufp,  //错误内容 
    ub4        bufsiz,  //bufp长度 
    ub4        type //传递的错误句柄类型 //=OCI_HTYPE_ERROR错误句柄  //=OCI_HTYPE_ENV: 环境句柄 
    ); 


    eg: 
    ub4    ub4RecordNo = 1; 
    OCIError* hError 
    sb4    sb4ErrorCode; 
    char   sErrorMsg[1024]; 
    if (OCIErrorGet(hError, ub4RecordNo++, NULL, &sb4ErrorCode, (OraText*) sErrorMsg, sizeof(sErrorMsg), OCI_HTYPE_ERROR) == OCI_SUCCESS) 
    printf(“error msg:%s\n”, sErrorMsg); 


    七:准备SQL语句 
    sword OCIStmtPrepare (  
    OCIStmt       *stmtp,//语句句柄   
    OCIError      *errhp, 
    CONST text    *stmt,  //SQL语句 
    ub4           stmt_len,   //语句长度 
    ub4           language,  //语句的语法格式=OCI_NTV_SYNTAX 
    ub4           mode //=OCI_DEFAULT 
    ); 
    eg: 
    char sSQL[1024]; 
    sprintf(sSQL, “select table_name from user_tables”); 
    swResult = OCIStmtPrepare(stmtp errhp,  (CONST OraText*)sSQL, strlen(sSQL), OCI_NTV_SYNTAX, OCI_DEFAULT); 
    if(swResult != OCI_SUCCESS && swResult != OCI_SUCCESS_WITH_INFO) 
    return FALSE; 

    八:绑定输入参数 
    OCIBindByName()----------------------按名绑定 
    OCIBindByPos()------------------------按位置绑定,建议一般按此方式绑定

    OCIBindArrayOfStruct()  -------------数组绑定,一般用于批量操作   
    OCIBindDynamic() Sets additional attributes after bind with OCI_DATA_AT_EXEC mode   
    OCIBindObject() Set additional attributes for bind of named data type  
    注: OCIBindArrayOfStruct 必须先用OCIBindByPos初始化,然后在OCIBindArrayOfStruct中定义每个参数所跳过的字节数。

    sword OCIBindByName (  
    OCIStmt       *stmtp, // 语句句柄 
    OCIBind       **bindpp,//结合句柄,=NULL 
    OCIError      *errhp, 
    CONST text    *placeholder,//占位符名称 
    sb4           placeh_len, // 占位符长度 
    dvoid         *valuep, //要替换的值的地址

    sb4           value_sz, // 要替换的值的最大字节数

    ub2           dty,  //绑定的类型

    dvoid         *indp, //指示符变量指针(sb2类型),单条绑定时为NULL, 
    ub2           *alenp, // 说明执行前后被结合的数组变量中各元素数据实际的长度,单条绑定时为NULL 
    ub2           *rcodep,// 列级返回码数据指针,单条绑定时为NULL 
    ub4           maxarr_len, //最多 的记录数,如果是单条绑定,则为0 
    ub4           *curelep, //实际的记录数,单 条绑定则为NULL 
    ub4           mode //=OCI_DEFAULT 
    );  


    sword OCIBindByPos ( 

    OCIStmt      *stmtp, 

    OCIBind      **bindpp, 
    OCIError     *errhp, 
    ub4          position,// 绑 定的位置 
    dvoid        *valuep, 
    sb4          value_sz, 
    ub2          dty, 
    dvoid        *indp, 
    ub2          *alenp, 
    ub2          *rcodep, 
    ub4          maxarr_len, 
    ub4          *curelep,  
    ub4          mode ); 


    sword OCIBindArrayOfStruct ( 
    OCIBind     *bindp,//绑定的结构句柄,由OCIBindByPos定义 
    OCIError    *errhp, 
    ub4         pvskip, //下一列跳过的字节数
    ub4         indskip,// 下一个指示器或数组跳过的字节数  
    ub4         alskip, //下一个实际值跳过的字节数 
    ub4         rcskip //下一个列级返回值跳过的字节数 
    ); 

    九:定义输出变量 
    OCIDefineArrayOfStruct()   Set additional attributes for static array define   
    OCIDefineByPos()   Define an output variable association   
    OCIDefineDynamic()   Sets additional attributes for define in OCI_DYNAMIC_FETCH mode   
    OCIDefineObject()   Set additional attributes for define of named data type   


    sword OCIDefineByPos (  
    OCIStmt     *stmtp, //语句句柄  
    OCIDefine   **defnpp,// 定义句柄—用于数组变量 
    OCIError    *errhp, 
    ub4         position,// 位置序号(从1 开始) 
    dvoid       *valuep, //输出的变量名 
    sb4         value_sz, // 变量长度 
    ub2         dty,  //数据类型 
    dvoid       *indp, // 指示器变量/指示器变量数组,如果此字段可能存在空值,则要指示器变量,否则单条处理时为NULL 
    ub2         *rlenp, // 提取的数据长度 
    ub2         *rcodep, //列级返回码数组指针 
    ub4         mode //OCI_DEFAULT 
    ); 


    sword OCIDefineArrayOfStruct (  
    OCIDefine   *defnp,// 由OCIDefineByPos定义的句柄 
    OCIError    *errhp, 
    ub4         pvskip, // 下一列跳过的字节数,一般就是结构的大小 
    ub4         indskip,//下一个指示器或结构 跳过的字节数,=0  
    ub4         rlskip, //下一个实际值跳过的字节数,=0 
    ub4         rcskip // 下一个列列级返回值跳过的字节数,=0 
    ); 

    sword OCIDefineObject ( 

    OCIDefine       *defnp, 
    OCIError        *errhp, 
    CONST OCIType   *type, 
    dvoid           **pgvpp,  
    ub4             *pvszsp,  
    dvoid           **indpp,  
    ub4             *indszp

     ); 
    eg: 
    单条查询 
    sql: select username,age from student  where username=:p1; 
    如果此字段有可能有空值,则 
    hDefine = NULL; 
    swResult = OCIDefineByPos(stmtp &hDefine, errhp, 1, tstd.username, sizeof(tstd.username), SQLT_CHR, & sb2aInd[0], NULL, NULL, OCI_DEFAULT); 
    如果此字段没有空值,则 
    hDefine = NULL; 
    swResult = OCIDefineByPos(stmtp &hDefine, errhp, 1, tstd.username, sizeof(tstd.username), SQLT_CHR, NULL, NULL, NULL, OCI_DEFAULT); 
    批量查询 
    select username,age from student  where age>;30; 
    hDefine = NULL; 
    swResult = OCIDefineByPos(stmtp, &hDefine, errhp, 1, &tstd[0].username,  
    sizeof(tstd[0].usenmae), SQLT_CHR, NULL, NULL, NULL, OCI_DEFAULT); 
    swResult = OCIDefineArrayOfStruct(hDefine, errhp, sizeof(tstd[0]), 0, 0, 0); 

    十:执行SQL语句 
    sword OCIStmtExecute (  
    OCISvcCtx           *svchp,  // 服务环境句柄 
    OCIStmt             *stmtp,  //语句句柄 
    OCIError            *errhp, 
    ub4                 iters, // ** 
    ub4                 rowoff, //** 
    CONST OCISnapshot   *snap_in, 
    OCISnapshot         *snap_out, 
    ub4                 mode //**
    ); 


    注: 
    1. iters:对于select语句,它说明一次执行读取到buffer中的记录行数,如果不能确定select语句所返回的行数,可将iters设置为 0,而对于其他的语句,iters表示这些语句的执行次数,此时iters不能为0。 
    2. rowoff:在多行执行时,该参数表示从所结合的数据变量中的第几条记录开始执行(即记录偏移量)。 
    3. mode:

    =OCI_DEFAULT:default模式 
    =OCI_DESCRIBE_ONLY:描述模式,只返回选择列表的描述信息,而不执行语句 
    =OCI_COMMIT_ON_SUCCESS:自动提交模式,当执行成功后,自动提交。 
    =OCI_EXACT_FETCH:精确提取模式。 
    =OCI_BATCH_ERRORS:批错误执行模式:用 于执行数组方式的操作,在此模式下,批量insert ,update,delete时,执行过程中任何一条记录错误不会导致整个 insert ,update,delete失败,系统自动会收集错误信息,而在非批错误方式下,其中的任何一条记录错误,将会导致整个操作失败。 
    Eg: 
    执行一次 
    swResult = OCIStmtExecute(svchp, stmtp,  errhp,1, 0, NULL, NULL, OCI_DEFAULT); 
    批量执行100次: 
    swResult = OCIStmtExecute(svchp, stmtp,  errhp, 100, 0, NULL, NULL, OCI_DEFAULT); 


    十一:提取结果 
    sword OCIStmtFetch ( 
    OCIStmt     *stmtp,//语句句柄 
    OCIError    *errhp,  
    ub4         nrows, //从当前位置处开始一次提取的记录数,对于数据变量,可 以>;1,否则不能>;1 
    ub2         orientation,//提取 的方向:OCI_FETCH_NEXT 
    ub4         mode //OCI_DEFAULT 
       ) 
    eg:
       while ((swResult=OCIStmtFetch stmtp errhp,1,OCI_FETCH_NEXT,OCI_DEFAULT)) != OCI_NO_DATA) 

    …… 


    十二:事务操作 
    开始一个事务 
    sword OCITransStart (  
    OCISvcCtx    *svchp,  
     OCIError     *errhp,  
    uword        timeout, //**  
    ub4          flags ); 
    注: 
    1. Timeout: 
    当flag=OCI_TRANS_RESUME:它表示还有多少秒事务将被激活 ,=OCI_TRANS_NEW: 事务响应的超时时间(秒) 
    2. Flags:指定一个新的事务还是已有事务 
    =OCI_TRANS_NEW:定义一个新的事务 
    =OCI_TRANS_RESUME 


    准备一个事务: 
    sword OCITransPrepare ( 

    OCISvcCtx    *svchp,  
    OCIError     *errhp, 
    ub4          flags );//OCI_DEFAULT 


    sword OCITransForget (  
    OCISvcCtx     *svchp,  
    OCIError      *errhp, 
    ub4           flags );//OCI_DEFAULT 


    断开一个事务: 
    sword OCITransDetach (  
    OCISvcCtx    *svchp, 
    OCIError     *errhp, 
    ub4          flags );//OCI_DEFAULT 


      提交一个事务: 
     sword OCITransCommit ( 
    OCISvcCtx    *svchp,  //服务环境句柄 
    OCIError     *errhp, 
    ub4          flags ); //OCI_DEFAULT 


    回滚一个事务 
    sword OCITransRollback ( 
    dvoid        *svchp,  
    OCIError     *errhp, 
    ub4          flags ); //OCI_DEFAULT 

     

    关于OCI编程的各个函数的参数详细解释可以参考Oracle官网的在线手册,非常好用。

    参考地址:http://download.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89857/toc.htm

     

    6:介绍一下OCI需要注意的地方

    一: 关于输出变量定义:如果在语句执行前就知道select语句的选择列表结构,则定义输出操作可在调用 OCISTMTExecute前进行,如果查询语句的参数为用户动态输入的,则必须在执行后定义。 


    二:OCI函数返回值: 
    OCI_SUCCESS –函数执行成功 (=0) 
    OCI_SUCCESS_WITH_INFO – 执行成功,但有诊断消息返回,可能是警告信息 
    OCI_NO_DATA—函数执行完成,但没有其他数据 
    OCI_ERROR—函数执行错 误 
    OCI_INVALID_HANDLE—传递给函数的参数为无效句柄,或传回的句柄无效 
    OCI_NEED_DATA—需要应用程序 提供运行时刻的数据 
    OCI_CONTINUE—回调函数返回代码,说明回调函数需要OCI库恢复其正常的处理操作 
    OCI_STILL_EXECUTING —服务环境建立在非阻塞模式,OCI函数调用正在执行中。 


    三: OCI连接有二种方式:Blocking(阻塞方式)和non_Blocking(非阻塞方式),阻塞方式就是当调用 OCI操作时,必须等到此OCI操 作完成后服务器才返回客户端相应的信息,不管是成功还是失败。非阻塞方式是当客户端提交OCI操作给服务器后,服务器立即返回 OCI_STILL_EXECUTING信息,而并不等待服务端的操作完成。 对于non- blocking方式,应用程序若收到一个OCI函数的返回值为 OCI_STILL_EXECUTING时必须再次对每一个OCI函数的返回值进行判 断,判断其成功与否。 可通过设置服务器属性为OCI_ATTR_NONBLOCKING_MODE来实现。系统默认方式为阻塞模式。


    四: OCI函数设置的模式有: 
      OCI_DEFUALT:使用OCI默认的环境 
      OCI_THREADED:线程环境下使用OCI 
      OCI_OBJECT:对象模式   //大家注意这个,当使用Orcale Spatial空间数据库时需要加上次模式。
      OCI_SHARED:共享模式 
      OCI_EVENTS 
      OCI_NO_UCB 
      OCI_ENV_NO_MUTEX:非互斥访问模式 
      其中模式可以用逻辑运算符进行迭加,将函数设置成多多种模式:如 mode=OCI_SHREADED| OCI_OBJECT 


    五: 当应用进程与服务器断开连接时,程序没有使用OCITransCommit()进行事务的提交,则所有活动的事务会自动回滚。 


    六: OCI重定义数据类型 
    typedef unsigned char  ub1;     
    typedef   signed char  sb1; 
    typedef unsigned short    ub2;   
    typedef   signed short    sb2; 
    typedef unsigned int  ub4;    
    typedef   signed int  sb4;   
    typedef         ub4      duword;     
    typedef         sb4      dsword;     
    typedef         dsword   dword; 
    七: 在SQL语句准备后,可以用OCIAttrSet(设置该语句的类型属性OCI_ATTR_STMT_TYPE,以后可读取语句属性,根据属性分别进行处理。 


    八: 批量绑定输入和定义输出参数:将数据存入一个静态数据组中。一次执行可以提交或读取多行记录值。 

    九:结合占位符和指示器变量: 
    ①占位符:在程序中,一些SQL语句需要在程序运行时才能确定它的语句数据,在设计时可用一个占位符来代替,当程序运行 时,在它准备好语句后,必须为每个占位符指定一个变量,即将占位符与程序变量地址结合,执行时,Oracle就从这些变量中读取数据,并将它们与SQL语 句一起传递给Oracle服务器执行。OCI结合占位符时,它将占位符与程序变量关联起来,并同时要指出程序变量的数据类型和数据长度。 
    如:select * from test where name=:p1 and age=:p2  (:p1和:p2为占位符 )
    ②指示器变量:由于在Oracle中,列值可以为NULL,但在C语言中没有NULL 值,为了能使OCI程序表达NULL列值,OCI函数允许程序为所执行语句中的结合变量同时关联一个指示符变量或指示符变量数组,以说明所结合的占位符是 否为NULL或所读取的列值是否为NULL,以及所读取的列值是否被截取。 
    除SQLT_NTY(SQL Named DataType)外,指示符变量或指示符变量数组的数据类型为sb2,其值说明: 
    作为输入变量时:(如insert ,update语句中) 
     =-1:OCI程序将NULL赋给Oracle表的列,忽略占位符结合的程序变量值 
    >;=0:应用程序将程序变量值赋给指定列 
    作 为输出变量时:(如select语句中) 
     =-2:所读取的列数据长度大于程序变量的长度,则被截取。 
     =-1:所读取的值为 NULL,输出变量的值不会被改变。 
    =0:数据被完整读入到指定的程序变量中 
    >0:所读取的列数据长度大于程序变量的长度,则被截 取,指示符变量值为所读取数据被截取前的实际长度 

     

    终于写完了,基本上就这么多了,希望对你有帮助。

    这里不能添加附加,直接贴代码的话,不是我的风格。下面给大家介绍可以学习和直接拿来用的代码地址:

    ①一个简单的连接Oracle数据库的代码,详细的说明了连接Oracle数据库的步骤(比较简单,没有输入与输出)

    参考地址:http://blog.csdn.net/sherlockhua/article/details/4353531     (适合初学者)

    ②我写的一个简单的OCI接口类,能批量读取和插入数据的类,里面涉及了一些比较常用函数的用法,值得看一下

    参考地址http://www.pudn.com/downloads371/sourcecode/database/detail1607043.html

    ③关于Oracle空间数据库的读取与写入  (比较全面,也比较复杂)

     参考地址:http://www.codesky.net/article/doc/200406/20040619864916.htm

    展开全文
  • OCI 编程

    千次阅读 2012-05-30 15:06:22
    OCI编程的一般过程  与OCI7.3中使用的宿主语言定义变量存储空间(很拗口,不用管它)的方式不同,OCI9以后已不再使用原有的变量结构来初始化及维护数据库的信息,而改用句柄的形式来和Oracle数据库进行交互。将常用的...
  • OCI--学习OCI编程

    千次阅读 2014-02-14 14:56:08
    学习OCI编程  2011-07-26 16:18:39| 分类: 数据库 | 标签:oci |举报|字号 订阅 最近公司做的一个项目,要处理海量数据,数据是存放在Oracle数据库里,刚开始用的是ADO访问,速度极慢,后来改用...
  • docker OCI runtime

    2018-12-29 19:14:00
    Open Container Initiative(OCI)目前有2个标准:runtime-...OCI规定了如何下载OCI镜像并解压到OCI filesystem bundle,这样OCI runtime就可以运行OCI bundle了。OCI(当前)相当于规定了容器的images和runtime的协议...
  • 一个简单的OCI函数,用于检查证书到期 部署中 fn deploy --app myapp 例子 echo '{\ "url": "https://apigateway.us-ashburn-1.oci.oraclecloud.com", \ "cn": "identity.us-ashburn-1.oraclecloud.com"\ }' | fn ...
  • <p>I installed XAMPP on Windows server. There are 2 application on my server: ... <p>Second Appilcation:...<p>How can I reduce or terminate this delay when connecting Oracle database using OCI? </div>
  • OCI env setting

    2018-01-01 18:57:40
    这次想写个OCI的测试程序,客户端OCI开发环境问题弄的老费劲了。 OCIServerAttach那显示错误: ORA-12154: TNS: 无法解析指定的连接标识符. 为了解决OCI编程环境问题,纠结了好久. 上网查ORA-12154错误,解决...
  • 一些节省时间的脚本将使Oracle Cloud Infrastructure管理更加容易。 随意分叉或提出改进建议。 有关某些说明和使用说明,请检查 。 oci_json_export.sh 将所有OCI元数据导出到JSON文件的工具。 。 oci_json_...
  • OCI相关知识

    2014-09-04 12:46:01
    网上很少有OCI的中文文档,一般英文文档大家可能看了也不太顺,我整理了一份,写了一些常用的OCI函数,供大家参考。 因为无法加附件,只好把内容贴上来了 一. Oracleoci工具包安装: $ORACLE_HOME\BIN:执行文件...
  • OCI常用函数详解

    千次阅读 2013-08-22 13:40:28
    一. Oracle oci工具包安装:  $ORACLE_HOME\BIN:执行文件和help文件  $ORACLE_HOME\OCI\INCLUDE:头文件  ...$ORACLE_HOME\OCI\LIB\MSVC: for MS Visual C++的OCI库 假如是unix下,对于ORACL
  • OCI常用函数

    千次阅读 2011-07-16 12:49:17
    一. Oracle oci工具包安装: $ORACLE_HOME\BIN:执行文件和help文件 $ORACLE_HOME\OCI\INCLUDE:头文件 $ORACLE_HOME\OCI\LIB\BC: for Borlanf C++的OCI库 $ORACLE_HOME\OC
  • OCI编程历程

    2019-09-27 17:57:29
    那哥们直接来了一句:“那东西没什么搞头,就调用些函数,然后做些错误处理”……… 我很是郁闷,回想起来实习时第一个能拿得出手的程序就是对OCI10封装库,当时为了测试效果一个人在机房里呆了一个多月,每天不停的...
  • oracle oci 例子

    千次阅读 2009-05-19 17:00:00
    Oracle oci工具包安装: $ORACLE_HOME/BIN:执行文件和help文件 $ORACLE_HOME/OCI/INCLUDE:头文件 $ORACLE_HOME/OCI/LIB/BC: for Borlanf C++的OCI库 $ORACLE_HOME/OCI/LIB/MSVC: for MS Visual C++的OCI库 ...
  • ZDM(零宕机时间迁移)19.7零宕机时间迁移工具,用于离线和在线物理数据库迁移 MV2实用程序-MV2ADB MV2OCI新!! MV2BUCKET新!! BCK2CLOUD新!! 2.01-115 2.0.2.6 1.0.1.4 2.0.3.24移至自治数据库,数据泵至自主...
  • 此次安装扩展耗费了我大半天的时间,特此记录其中的安装过程,希望这篇教程能够帮助到大家,避免大家在安装的过程中少走弯路。 CentOS服务器上已有相关环境:apache2、php7需要安装:1.oracle客户端、2.oci8扩展、3...
  • 学习OCI编程

    2014-10-14 18:12:00
    最近公司做的一个项目,要处理海量数据,数据是存放在Oracle数据库里,刚开始用的是ADO访问,速度极慢,后来改用Proc,效果还是不如人意,最后才用的OCI。因为之前对OCI不了解过,经2个星期的努力,终于完成了对...
  • oracle常用oci函数

    2015-06-26 09:57:35
     Oracle oci工具包安装:  $ORACLE_HOME\BIN:执行文件和help文件  $ORACLE_HOME\OCI\INCLUDE:头文件  $ORACLE_HOME\OCI\LIB\BC: for Borlanf C++的OCI库  $ORACLE_HOME\OCI\LIB\MSVC: for MS Visual ...
  • 这段自学 OCI 编程,感觉网上这方面的资料其实也不少,只是不是很容易找到。本文就这段时间OCI 函数的学习及所收集的资料进行整合,用于帮助那些刚刚接触 OCI 编程的程序员,及 OCI 编程专家做以讨论及研究。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,339
精华内容 3,335
关键字:

oci时间