为了使用 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 必须指向定义在 或 区域中的数据源,而不是 重新命名的数据源。