OCCI小程序示例

2018-02-02 19:51:03来源:cnblogs.com作者:帅老人点击

分享

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 CMyDatabase10 {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     try18     {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 << "@" << connStr25                 << " 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.soDynamic 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.soDynamic 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开发环境的安装和配置》

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台