非 DBCP 的解决方案
这些方案或者使用一个单独的数据库连接(建议仅作测试用!),或者使用其他一些池化技术。
Oracle 8i 与 OCI 客户端
简介
虽然并不能严格地解决如何使用 OCI 客户端来创建 JNDI 数据源的问题,但这些注意事项却能和上文提到的 Oracle 与 DBCP 解决方案结合起来使用。
为了使用 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 问题。
有报告称,忽略从 otn 网站下载的驱动,使用 $ORAHOME\jdbc\lib 目录中的 class12.zip 文件,同样能够正常运作。
接下来,你可能还会遇到另一个错误消息: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 区域的资源将会被服务器的各个上下文所共享。
JNDI 资源命名和 Realm 交互
为了让 Realm 能运作,realm 必须指向定义在 <GlobalNamingResources> 或 <Context> 区域中的数据源,而不是<ResourceLink> 重新命名的数据源。
感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程