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