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开发环境的安装和配置》