OCCI小程序示例

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。

 

3.3.  ‘std::string’ is ambiguous ‘

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

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

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

 

对于这个“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开发环境的安装和配置》

(0)
上一篇 2022年3月22日
下一篇 2022年3月22日

相关推荐