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。
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是在編譯動態庫時指定的,名字與文件名可以不完全一致。