為了使用 OCI 驅(qū)動(dòng),應(yīng)該先安裝一個(gè) Oracle 客戶。你應(yīng)該已經(jīng)通過光盤安裝好了 Oracle 8i(8.1.7)客戶端,并從 otn.oracle.com 下載了適用的 JDBC/OCI 驅(qū)動(dòng)(Oracle8i 8.1.7.1 JDBC/OCI 驅(qū)動(dòng))。
將 classes12.zip 重命名為 classes12.jar 后,將其復(fù)制到 $CATALINA_HOME/lib 中。根據(jù) Tomcat 的版本以及你所使用的 JDK,你可能還必須該文件中的刪除 javax.sql.* 類。
確保在 $PATH 或 LD_LIBRARY_PATH(可能在 $ORAHOME\bin)目錄下存在 ocijdbc8.dll 或 .so 文件,另外還要確認(rèn)能否使用 System.loadLibrary("ocijdbc8"); 這樣的簡(jiǎn)單測(cè)試程序加載本地庫。
下面你應(yīng)該創(chuàng)建一個(gè)簡(jiǎn)單測(cè)試用 servlet 或 jsp,其中應(yīng)該包含以下關(guān)鍵代碼:
DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());
conn =
DriverManager.getConnection("jdbc:oracle:oci8:@database","username","password");
目前數(shù)據(jù)庫是 host:port:SID 形式,如果你試圖訪問測(cè)試用servlet/jsp,那么你會(huì)得到一個(gè) ServletException 異常,造成異常的根本原因在于 java.lang.UnsatisfiedLinkError:get_env_handle。
分析一下,首先 UnsatisfiedLinkError 表明:
JDBC 類文件和 Oracle 客戶端版本不匹配。消息中透露出的意思是沒有找到需要的庫文件。比如,你可能使用 Oracle 8.1.6 的 class12.zip 文件,而 Oracle 客戶端版本則是 8.1.5。classeXXXs.zip 文件必須與 Oracle 客戶端文件版本相一致。
出現(xiàn)了一個(gè) $PATH, LD_LIBRARY_PATH 問題。
接下來,你可能還會(huì)遇到另一個(gè)錯(cuò)誤消息:ORA-06401 NETCMN: invalid driver designator。
Oracle 文檔是這么說的:“異常原因:登錄(連接)字符串包含一個(gè)不合法的驅(qū)動(dòng)標(biāo)識(shí)符。解決方法:修改字符串,重新提交。”所以,如下面這樣來修改數(shù)據(jù)庫(host:port:SID)連接字符串:(description=(address=(host=myhost)(protocol=tcp)(port=1521))(connect_data=(sid=orcl)))
下面是一些 Web 應(yīng)用在使用數(shù)據(jù)庫時(shí)經(jīng)常會(huì)遇到的問題,以及一些應(yīng)對(duì)技巧。
Tomcat 運(yùn)行在 JVM 中。JVM 周期性地會(huì)執(zhí)行垃圾回收(GC),清除不再使用的 Java 對(duì)象。當(dāng) JVM 執(zhí)行 GC 時(shí),Tomcat 中的代碼執(zhí)行就會(huì)終止。如果配置好的數(shù)據(jù)庫連接建立的最長(zhǎng)時(shí)間小于垃圾回收的時(shí)間,數(shù)據(jù)庫連接就會(huì)失敗。
在啟動(dòng) Tomcat 時(shí),將 -verbose:gc 參數(shù)添加到 CATALINA_OPTS 環(huán)境變量中,就能知道垃圾回收所占用的時(shí)間了。在啟用 verbose:gc 后, $CATALINA_BASE/logs/catalina.out 日志文件就能包含每次垃圾回收的數(shù)據(jù),其中也包括它所占用的時(shí)間。
正確調(diào)整 JVM 后,垃圾回收可以做到在 99% 的情況下占用時(shí)間不超過 1 秒。剩余的情況則只占用幾秒鐘的時(shí)間,只有極少數(shù)情況下 GC 會(huì)占用超過 10 秒鐘的時(shí)間。
保證讓數(shù)據(jù)庫連接超時(shí)設(shè)定在 10~15 秒。對(duì)于 DBCP,可以使用 maxWaitMillis 參數(shù)來設(shè)置。
當(dāng)某一請(qǐng)求從連接池中獲取了一個(gè)數(shù)據(jù)庫連接,然后關(guān)閉了它兩次時(shí),往往會(huì)出現(xiàn)這樣的異常消息。使用連接池時(shí),關(guān)閉連接,就會(huì)把它歸還給連接池,以便之后其他的請(qǐng)求能夠重用該連接,而并不會(huì)關(guān)閉連接。Tomcat 使用多個(gè)線程來處理并發(fā)請(qǐng)求。下面這個(gè)范例就演示了,在 Tomcat 中,一系列事件導(dǎo)致了這種錯(cuò)誤。
運(yùn)行在線程 1 中的請(qǐng)求 1 獲取了一個(gè)連接。
請(qǐng)求 1 關(guān)閉了數(shù)據(jù)庫連接。
JVM 將運(yùn)行的線程切換為線程 2。
線程 2 中運(yùn)行的請(qǐng)求 2 獲取了一個(gè)數(shù)據(jù)庫連接。
(同一個(gè)數(shù)據(jù)庫連接剛被請(qǐng)求 1 關(guān)閉)
JVM 又將運(yùn)行的線程切換回為線程 1。
請(qǐng)求 1 第二次關(guān)閉了數(shù)據(jù)庫連接。
JVM 將運(yùn)行的線程切換回線程 2。
請(qǐng)求 2 和線程 2 試圖使用數(shù)據(jù)庫連接,但卻失敗了。因?yàn)?/span>請(qǐng)求 1 已經(jīng)關(guān)閉了它。
}
Connection conn = null;
Statement stmt = null; // Or PreparedStatement if needed
ResultSet rs = null;
try {
conn = ... get connection from connection pool ...
stmt = conn.createStatement("select ...");
rs = stmt.executeQuery();
... iterate through the result set ...
rs.close();
rs = null;
stmt.close();
stmt = null;
conn.close(); // Return to connection pool
conn = null; // Make sure we don't close it twice
} catch (SQLException e) {
... deal with errors ...
} finally {
// Always make sure result sets and statements are closed,
// and the connection is returned to the pool
if (rs != null) {
try { rs.close(); } catch (SQLException e) { ; }
rs = null;
}
if (stmt != null) {
try { stmt.close(); } catch (SQLException e) { ; }
stmt = null;
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) { ; }
conn = null;
}
注意,雖然在上面的說明中,把 JNDI 聲明放在一個(gè) Context 元素里面,但還是有可能(而且有時(shí)更需要)把這些聲明放在服務(wù)器配置文件的 GlobalNamingResources 區(qū)域。被放置在 GlobalNamingResources 區(qū)域的資源將會(huì)被服務(wù)器的各個(gè)上下文所共享。
為了讓 Realm 能運(yùn)作,realm 必須指向定義在 或 區(qū)域中的數(shù)據(jù)源,而不是 重新命名的數(shù)據(jù)源。