欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

OCCI小程序示例

程序员文章站 2022-07-02 16:30:31
1. 引言 1.1. 背景 本文延续《OCCI开发环境的安装和配置》一文,目的主要是搭建一个可用的OCCI开发环境。作为环境的验证,本文给出一个小例子,获取Oracle数据库的系统时间的小程序,在OCCI环境开发和运行。 同时,总结一下在测试过程中遇到的所有问题和使用的知识,不仅限于OCCI,包括I ......

1.  引言

1.1.  背景

本文延续《OCCI开发环境的安装和配置》一文,目的主要是搭建一个可用的OCCI开发环境。作为环境的验证,本文给出一个小例子,获取Oracle数据库的系统时间的小程序,在OCCI环境开发和运行。

同时,总结一下在测试过程中遇到的所有问题和使用的知识,不仅限于OCCI,包括IDE、编译器、操作系统(Linux)。

1.2.  系统环境

数据库:Oracle12c

客户端:instantclient_12_2

操作系统:Ubuntu16.04.3 Linux kernel 4.4.0-112-generic

IDE:Eclipse Oxygen.2 Release (4.7.2) Build id: 20171218-0600

编译器:gcc 4.8.5

2.  程序代码

2.1.  主程序

main.cpp

 1 #include <string>
 2 #include <iostream>
 3 #include "CMyDatabase.h"
 4  
 5 Int main (int argc, char * argv[])
 6 
 7 {
 8 
 9   CMyDatabase stdb;
10   std::string user = "system";
11   std::string passwd = "Oracle123";
12   std::string connStr = "storcdb";
13  
14   stdb.connect (user, passwd, connStr);
15   stdb.getSystemDateFromDatabase ();
16 
17   return 0;
18 
19 }

主程序很简单。CMyDatabase类是我封装的Oracle数据库访问用的类,在主程序中,我们只要知道我们通过这个类连接数据库(connect),并且可以获得数据库系统的时间(getSystemDateFromDatabase)。

在连接数据库时,我们需要提供数据库访问的用户名和密码,以及标识数据库的网络服务名称或者说数据库连接字符串。可以参考《OCCI开发环境的安装和配置》一文中的4.1节以及4.2节末尾使用sqlplus测试数据库连接的部分内容。

2.2.  CMyDatabase

CMyDatabase类主要封装了Oracle的Environment和Connection类,由于这两个类(OCCI中其他类如Statement类也存在这种情况)的创建都是以指针的方式返回,需要进行销毁,所以,将这种资源类封装在管理类里面,本例为CMyDatabase,由CMyDatabase负责创建和销毁Environment和Connection类的实例,从而保证资源的正确使用,即创建和销毁。

CMyDatabase.h

 1 #ifndef CMYDATABASE_H_
 2 #define CMYDATABASE_H_
 3 
 4 #include <string>
 5 #include <iostream>
 6 #include <occi.h>
 7 using namespace oracle::occi;
 8 
 9 class CMyDatabase
10 {
11 public:
12     CMyDatabase() : m_env(NULL), m_conn(NULL)
13     {
14     }
15     virtual ~CMyDatabase();
16     void connect(const std::string & user, const std::string & passwd,
17         const std::string & connStr);
18     void getSystemDateFromDatabase();
19 private:
20     Environment * m_env;
21     Connection * m_conn;
22 };
23 
24 #endif /* CMYDATABASE_H_ */

 

CMyDatabase.cpp

 1 #include "CMyDatabase.h"
 2 
 3 CMyDatabase::~CMyDatabase()
 4 {
 5     if (NULL != m_conn)
 6     {
 7         m_env->terminateConnection(m_conn);
 8         m_conn = NULL;
 9     }
10     if (NULL != m_env)
11         Environment::terminateEnvironment(m_env);
12 }
13 
14 void CMyDatabase::connect(const std::string & user, const std::string & passwd,
15     const std::string & connStr)
16 {
17     try
18     {
19         m_env = Environment::createEnvironment();
20         m_conn = m_env->createConnection(user, passwd, connStr);
21     }
22     catch (SQLException & e)
23     {
24         std::cout << "using " << user << "/" << passwd << "@" << connStr
25                 << " connect to database." << std::endl;
26         std::cout << "*** " << e.getErrorCode() << ": " << e.getMessage()
27                 << std::endl;
28     }
29 }
30 
31 void CMyDatabase::getSystemDateFromDatabase()
32 {
33     Statement * stmt = m_conn->createStatement();
34     ResultSet * rs = stmt->executeQuery(
35             "select to_char(sysdate, 'YYYY-MM-DD HH:MI:SS') from dual");
36     rs->next();
37     std::cout << rs->getString(1) << std::endl;
38     stmt->closeResultSet(rs);
39     m_conn->terminateStatement(stmt);
40 }

在void CMyDatabase::getSystemDateFromDatabase();函数中创建一个Statement对象,并执行一个获得数据库系统时间的SQL语句,执行之后会返回一个ResultSet(结果集),访问结果集之前需要调用next()函数,实际上只有该函数返回Status::DATA_AVAILABLE(如果是流类型的数据,返回值为Statue::    STREAM_DATA_AVAILABLE)的时候才表明有数据,可以获得结果集中的数据。另外,对于数据库函数的使用要将其放入异常捕获代码块中,就像CMyDatabase::connect函数中写的一样。在这里我省略的必要的判断,在正式的代码中一定不要忘记。

 

3.  问题分析

本文不打算讲解使用Eclipse创建这个C++项目的过程,我相信有很多资料会讲,而且,我相信你是一个有经验的人,即使你是初学者,我认为通过摸索你也能够很快地把项目正确地创建出来。我们都是程序员,有这智商。

因此,这一章主要讲解一下这个例子中所遇到的一些问题,希望对大家有所帮助。

3.1.  安装Ubuntu16.04

如果你需要安装一个全新的Ubuntu16.04,我建议你直接到官方网站下载的最新版本的Ubuntu16.04.3(ubuntu-16.04.3-desktop-amd64.iso),当然,也可以尝试更高的版本。在最初的16.04安装包中存在问题,执行系统更新(sudo apt-get update)的时候会崩溃,需要将libappstream3包清除掉(sudo apt-get purge libappstream3)或者手工下载libappstream包进行安装,解决该问题。

3.2.  为什么是g++-4.8

当你安装了Ubuntu16.04或者更高的版本时,你的系统默认或者执行sudo apt-get install g++之后所安装的g++版本均在5.0以上。由于OCCI库是在gcc-4下编译,在gcc-5(5以上版本未测试)上编译后,程序执行会崩溃。具体的原因我目前还不清楚,推测与两个版本的std::string实现有关。

基于以上原因,我们需要为系统安装g++-4.8:

sudo apt-get install g++-4.8

如果是经过g++ 5.4编译过的程序,在连接数据库时会收到ORA-24960异常,并且程序崩溃转储。具体异常错误如下:

ORA-24960: the attribute  OCI_ATTR_USERNAME is greater than the maximum allowable length of 255 

......

 

 

 

如果使用makefile来编译,那么制定编译和链接工具为g++-4.8就可以了;如果使用Eclipse编译,同样也需要配置编译和链接工具为g++-4.8。在项目上点击右键选择Properties --> C/C++ Build --> Settings,修改如下图标记的位置为g++-4.8。

OCCI小程序示例

 

3.3.  'std::string' is ambiguous '

当在系统中安装了g++-4.8之后,由于有多个gcc版本的存在,Eclipse会找到多个gcc版本的头文件,所以,Eclipse会对你用到的类型提示ambiguous,例如std::string。

OCCI小程序示例

当右键点击查看定义时,会弹出选择具体头文件的窗口,如下图所示。

OCCI小程序示例

如同配置g++-4.8一样,在Eclipse项目上鼠标右键点击打开Properties --> C/C++ Build --> Settings,按照下图示例设置“模棱两可”的头文件引用。

 OCCI小程序示例

对于这个“include files”设置,除了解决头文件选择的二义性之外,是否还有其他什么作用?

4.  知识延伸

4.1.  SONAME

在前一篇文章《OCCI开发环境的安装和配置》中提到动态库libclntshcore.so.12.1是否需要建立没有版本号的符号链接这个问题。通过对动态库的SONAME进行分析可以得到答案。

使用readelf查看动态库的SONAME,我们会发现libcclntshcore.so.12.1动态库的SONAME为“libclntshcore.so.12.1”,如下图:

readelf -d /opt/oracle/instantclient_12_2/libclntshcore.so

Dynamic section at offset 0x3b8f80 contains 30 entries:

  标记        类型                         名称/值

 0x0000000000000001 (NEEDED)             共享库:[libdl.so.2]

 0x0000000000000001 (NEEDED)             共享库:[libm.so.6]

 0x0000000000000001 (NEEDED)             共享库:[libpthread.so.0]

 0x0000000000000001 (NEEDED)             共享库:[libnsl.so.1]

 0x0000000000000001 (NEEDED)             共享库:[librt.so.1]

 0x0000000000000001 (NEEDED)             共享库:[libaio.so.1]

 0x0000000000000001 (NEEDED)             共享库:[libresolv.so.2]

 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]

 0x0000000000000001 (NEEDED)             共享库:[ld-linux-x86-64.so.2]

 0x0000000000000001 (NEEDED)             共享库:[libgcc_s.so.1]

 0x000000000000000e (SONAME)             Library soname: [libclntshcore.so.12.1]

……

当我们的代码不直接引用该动态库时,我们就不需要定义一个没有版本号的符号链接,因为使用到该动态库的其他程序会直接使用SONAME定义的名称找到该动态库。例如,我们readelf查看一下libclntsh.so.12.1的动态库引用情况:

readelf -d /opt/oracle/instantclient_12_2/libclntsh.so

Dynamic section at offset 0x3859bc0 contains 35 entries:

  标记        类型                         名称/值

 0x0000000000000001 (NEEDED)             共享库:[libmql1.so]

 0x0000000000000001 (NEEDED)             共享库:[libipc1.so]

 0x0000000000000001 (NEEDED)             共享库:[libnnz12.so]

 0x0000000000000001 (NEEDED)             共享库:[libons.so]

 0x0000000000000001 (NEEDED)             共享库:[libdl.so.2]

 0x0000000000000001 (NEEDED)             共享库:[libm.so.6]

 0x0000000000000001 (NEEDED)             共享库:[libpthread.so.0]

 0x0000000000000001 (NEEDED)             共享库:[libnsl.so.1]

 0x0000000000000001 (NEEDED)             共享库:[librt.so.1]

 0x0000000000000001 (NEEDED)             共享库:[libaio.so.1]

 0x0000000000000001 (NEEDED)             共享库:[libresolv.so.2]

 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]

 0x0000000000000001 (NEEDED)             共享库:[ld-linux-x86-64.so.2]

 0x0000000000000001 (NEEDED)             共享库:[libclntshcore.so.12.1]

 0x0000000000000001 (NEEDED)             共享库:[libgcc_s.so.1]

 0x000000000000000e (SONAME)             Library soname: [libclntsh.so.12.1]

 

从命令结果来分析,libclntsh.so.12.1使用了libclntshcore.so.12.1,它可以通过SONAME找到这个文件。

对于libclntsh.so.12.1了来说,我们需要在程序中引用该动态库,所以,需要为该动态库文件创建符号链接,使编译器可以找到该文件。

这种通过SONAME来确定具体的动态库的方式,使得在同一个操作系统上存在同一个动态库的多个版本成为可能,满足不同应用对不同版本动态库的依赖要求。

SONAME是在编译动态库时指定的,名字与文件名可以不完全一致。

OCCI小程序示例

前一篇《OCCI开发环境的安装和配置》