了解如何利用连接和语句池特性来提高 Oracle 驱动的 JDBC 程序的性能。
作者:Yuli Vasiliev2009 年 4 月发布
使用诸如连接池和语句池等池技术可以显著提高数据库密集型应用程序的性能,因为这样可以实现对象重用,而无需花费时间和资源重新创建对象。
如果应用程序与数据库频繁交互并且经常使用相同的参数重新建立连接,那么重用表示应用程序使用的物理数据库连接的数据库连接对象可显著提高性能。反之,如果应用程序与其基础数据库很少连接,您不会因使用连接池获益。实际上,如果池的设置(例如,允许的最大或最小连接数限制)针对特定应用程序进行了优化,许多数据库密集型应用程序都可以因使用连接池获益。
与连接池一样,语句池也是用于提高应用程序性能的技术。通过在应用程序运行期间多次执行语句池,可以进一步提高性能。然而,我们应该意识到语句池并不是解决性能问题的灵丹妙药。如果对每条语句进行缓存而不辨别其在程序中执行的次数,则不可能获得任何性能改进。实际上,由于将语句放入缓存并保存在缓存中将产生开销,因此对在程序执行期间仅执行一次的语句进行缓存将降低性能。
本文将向您介绍如何利用连接池和语句池提升通过 Oracle JDBC 瘦驱动程序与 Oracle 数据库交互的数据密集型 Java 数据库连接 (JDBC) 程序的性能。本文将特别介绍 Oracle Universal Connection Pool (UCP) for JDBC,它提供了适用于缓存 JDBC 连接的全功能连接池实现。最后,本文将讨论如何通过使用语句池获益,需要利用特定于 Oracle 的 JDBC 驱动程序的特性以及新的 JDBC 4.0 方法,这些方法已添加到 Statement 接口并且在支持 Java 开发工具包 (JDK) 1.6 及更高版本的 Oracle JDBC 驱动程序中可用。
设置工作环境
要使用本文中的示例并访问 Oracle 数据库,您的开发计算机上需要安装以下软件组件(请参见“下载”portlet 以获得链接):
- JDK 1.6
- 支持 JDK 1.6 的 Oracle JDBC 瘦驱动程序
- Oracle 通用连接池库
Oracle JDBC 瘦驱动程序是一个 Type IV JDBC 驱动程序,这意味着它独立于平台,并且在与 Oracle 数据库交互的客户端上不需要任何额外的 Oracle 软件。因此,您可以从 页面下载包含相应瘦驱动程序版本类别的 JAR 文件,然后将该驱动程序安装在您的计算机上,无需安装/升级任何其他的 Oracle 软件。要安装该驱动程序,您只需将其 JAR 文件复制到本地文件系统,然后将这些 JAR 的路径包括在 CLASSPATH 环境变量中。例如,您可能包括以下路径:
ORACLE_HOME/jdbc/lib/ojdbc6.jarORACLE_HOME/jlib/orai18n.jar
如果您的计算机上已经安装了 Oracle 数据库,则瘦驱动程序也已经随 Oracle 数据库安装到您的计算机上。但是,由于瘦驱动程序独立于任何其他 Oracle 软件,因此您可以通过使用相应的 JAR 文件(可以在 页面找到)轻松升级到该驱动程序的最新版本。
UCP 是从 11.1.0.7 版开始引入 Oracle 数据库 11g 的一个新特性。该特性从 Oracle 应用服务器 11g 第 1 版开始就包含在 Oracle 应用服务器中。如果您使用的是未装载 UCP 的 JAR 文件(名为 ucp.jar)的旧版本软件,或者您希望升级到最新的 UCP 版本,可以从 页面获取 ucp.jar。该程序包包含 UCP 的类,以便于将其包括在类路径中来启用该特性。所包括的路径可能如下所示:
ORACLE_HOME/ucp/lib/ucp.jar
使用 UCP 缓存 JDBC 连接
如果您要开发数据库密集型应用程序,可能会因使用连接池获益。因为这样您能够重用连接,而不是在每次请求连接时都重新创建一个新连接。连接池节约了创建新数据库连接所需的资源,并提高了应用程序的性能,因为创建新连接始终是一个性能密集型操作。
Oracle Universal Connection Pool for JDBC 表示一个用于缓存 JDBC 连接的全功能实现。UCP 是一个非常有用的特性,它将使您可以重用连接对象,从而可以提高获取连接过程的速度并节约打开新数据库连接所需的资源。
假设您希望创建一个 UCP JDBC 连接池来重用到 HR/HR Oracle 数据库示例模式的已建立连接。以下程序是一个 UCP JDBC 连接池实际运行的简单示例,将向您展示如何完成此操作。您将首先创建一个支持池的数据源实例,然后设置连接和池的属性。完成后,您将从池中借用一个连接,然后使用该连接与数据库交互。最后,您将关闭该连接,将其返回到池。
/**A simple example illustrating aUCP JDBC connection in action*/import java.sql.*;import oracle.ucp.jdbc.PoolDataSourceFactory;import oracle.ucp.jdbc.PoolDataSource;public class UcpConnection { public static void main(String args[]) throws SQLException { try { //Creating a pool-enabled data source PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); //Setting connection properties of the data source pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); pds.setURL("jdbc:oracle:thin:@//localhost:1521/XE"); pds.setUser("hr"); pds.setPassword("hr"); //Setting pool properties pds.setInitialPoolSize(5); pds.setMinPoolSize(5); pds.setMaxPoolSize(10); //Borrowing a connection from the pool Connection conn = pds.getConnection(); System.out.println("\nConnection borrowed from the pool"); //Checking the number of available and borrowed connections int avlConnCount = pds.getAvailableConnectionsCount(); System.out.println("\nAvailable connections: " + avlConnCount); int brwConnCount = pds.getBorrowedConnectionsCount(); System.out.println("\nBorrowed connections: " + brwConnCount); //Working with the connection Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select user from dual"); while(rs.next()) System.out.println("\nConnected as: "+rs.getString(1)); rs.close(); //Returning the connection to the pool conn.close(); conn=null; System.out.println("\nConnection returned to the pool"); //Checking the number of available and borrowed connections again avlConnCount = pds.getAvailableConnectionsCount(); System.out.println("\nAvailable connections: " + avlConnCount); brwConnCount = pds.getBorrowedConnectionsCount(); System.out.println("\nBorrowed connections: " + brwConnCount); } catch(SQLException e) { System.out.println("\nAn SQL exception occurred : " + e.getMessage()); } }}
这里值得注意的是关闭连接时的变化。以上程序的输出阐释了关闭从 UCP JDBC 连接池中借用的连接将使该连接返回到池,以供下一次连接请求使用。
该应用程序的输出应如下所示:
Connection borrowed from the pool Available connections: 4 Borrowed connections: 1 Connected as: HR Connection returned to the pool Available connections: 5 Borrowed connections: 0
使用 JNDI 借用连接
您还可以提前创建支持池的数据源并将其绑定到 Java 命名和目录接口 (JNDI) 上下文和逻辑名称,而不是像在之前的示例中那样即时创建。将数据源注册到 JNDI 后,可以通过执行 JNDI 查找(指定与数据源绑定的 JNDI 名称)来获取其实例。
假设您要注册一个支持池的数据源以重用到 HR/HR 数据库模式的连接,并将该数据源与 JNDI 树中的逻辑名称 jdbc/HRPool 相关联。为此,必须创建一个表示上述数据源的 PoolDataSource 对象,设置其属性,然后将其注册到一个 JNDI 命名服务。使用以下 Java 程序可以完成此操作:
/**An example of how you can register* a pool-enabled data source with JNDI*/import oracle.ucp.jdbc.PoolDataSourceFactory;import oracle.ucp.jdbc.PoolDataSource;import javax.naming.*; import java.util.Hashtable; public class JNDIRegister { public static void main(String argv[]) { try { //Creating a pool-enabled data source instance and setting its properties PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); pds.setURL("jdbc:oracle:thin:@//localhost:1521/XE"); pds.setUser("hr"); pds.setPassword("hr"); pds.setInitialPoolSize(5); pds.setMinPoolSize(5); pds.setMaxPoolSize(10); //Registering the data source with JNDI Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory"); Context ctx = new InitialContext(env); ctx.bind("jdbc/HRPool", pds); } catch (Exception e) { System.out.println(e); } } }
必须先设置 Sun 的文件系统 JNDI 服务提供程序(可在 下载),然后才能运行此程序。确保将以下 JAR 文件添加到类路径中,这样才能够运行上述程序:
install_dir/sun/lib/fs/fscontext.jar;install_dir/sun/lib/fs/providerutil.jar
运行上述程序之后,就可以在 Java 应用程序(无论是 JavaServer 页面、servlet,还是独立应用程序)中使用 jdbc/HRPool 支持池的数据源了。以下是一个使用该数据源的独立 Java 应用程序:
/**An example of a JNDI lookup for* a pool-enabled data source */import java.sql.*; import oracle.ucp.jdbc.PoolDataSource;import javax.naming.*; import java.util.Hashtable; public class JNDILookup { public static void main(String argv[]) { PoolDataSource pds; //Performing a lookup for a pool-enabled data source registered in JNDI tree try { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory"); Context ctx = new InitialContext(env); pds = (PoolDataSource) ctx.lookup("jdbc/HRPool"); } catch (NamingException eName) { System.out.println("Cannot look up " + "jdbc/HRPool" + ": " +eName); return; } //Borrowing a connection from the data source returned by the JNDI lookup try { Connection conn = pds.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select user from dual"); while(rs.next()) System.out.println("\nConnected as: "+rs.getString(1)); if (conn != null) conn.close(); } catch (SQLException eSQL) { System.out.println("Cannot obtain a connection: " + eSQL); } return; }}
在上面程序中,您首先要初始化 JNDI 上下文,然后使用它执行针对 jdbc/HROracle 支持池的数据源的 JNDI 查找。接下来,您将借用来自 JNDI 查找返回的数据源实例的连接,并使用该连接执行针对数据库的查询。
您无疑已经意识到,本节讨论的方法简化了使用连接池的流程。注册支持池的数据源一次,然后在需要时通过 JNDI 查找获取它的一个实例。这样就无需在每次初始化时都设置连接池属性了。您已获取了一个具有预定义属性的池实例。
高可用性和高性能
需要强调的是,UCP 支持与 Oracle 真正应用集群 (RAC) 无关的新的 JDBC 4.0 高可用性和高性能特性(如池刷新和连接验证)。因此,不需要 Oracle RAC 数据库。
而且,UCP 能够验证借用连接。验证借用连接是一项非常有用的技术,因为它使您能够在使用前检查连接是否有效。为解决该问题,UCP JDBC 连接池实例中添加了布尔类型的 ValidateConnectionOnBorrow 属性,您需要使用 setValidateConnectionOnBorrow 方法将其设为 true:
pds.setValidateConnectionOnBorrow(true);
然后,需要指定一条要执行的 SQL 语句,以确保该连接仍然有效。可以使用 setSQLForValidateConnection 方法完成此操作:
pds.setSQLForValidateConnection("select user from dual");
使用 Oracle JDBC 驱动程序时,无需设置 SQLForValidateConnection 属性 — 池将在内部执行 ping 操作来测试该借用连接的有效性。
验证借用连接是一件非常有益的事情,但是如果在成功验证借用连接后,连接失效该怎么办?有没有方法在借用后验证连接?为解决此问题,JDBC 4.0 规范将 isValid 方法添加到 Connection 接口,使您能够在需要的时候测试连接的有效性。
更进一步地讲,UCP for JDBC 提供了 oracle.ucp.jdbc.ValidConnection 接口,其中包括两个方法:isValid 和 setInvalid。与使用递归或迭代(循环)实现的重试机制结合使用时,这两个方法尤为有用。例如,您可以实现一个借用连接然后使用连接的方法,对其自身使用一个递归调用以防由于连接失效导致操作无法完成。实现这样的递归机制时需要记住的是,必须能够限制要进行的递归次数,并且每进行一次新的递归调用必须减少该次数,从而避免可能出现的无限循环。
以下是作为示例的一个简单程序,将演示如何结合使用 oracle.ucp.jdbc.ValidConnection 接口方法与基于递归的重试机制。
/**An example of validating connections on borrow;*this also shows the use of the ValidConnection interface's methods:*isValid and setInvalid methods in combination with a retry mechanism*/import java.sql.*; import oracle.ucp.jdbc.PoolDataSource;import oracle.ucp.jdbc.ValidConnection;import javax.naming.*; import java.util.Hashtable; public class ConnectionValidating { public static void main(String argv[]) { PoolDataSource pds; //Looking up for the jdbc/HRPool pool-enabled data source registered in JNDI tree ... //for actual code see the JNDI lookup example //discussed in the Borrowing a Connection with JNDI section earlier ... try { //Instructing the pool to validate connections on borrow pds.setValidateConnectionOnBorrow(true); //Calling the getUser method that borrows a connection from the pool //limiting the number of recursive calls to 3 System.out.println("\nConnected as :"+getUser(pds, 3)); } catch (SQLException eSQL) { System.out.println("\nSQLException: " + eSQL); return; } } //This method borrows a connection from the pool and will make a recursive call //if it turns out that the borrowed connection has become unusable private static String getUser (PoolDataSource pds, int recursiveCalls) throws SQLException { Connection conn = null; Statement stmt = null; ResultSet rs = null; String user = null; try { //Borrowing a connection from the pool conn = pds.getConnection(); //Working with the connection stmt = conn.createStatement(); rs = stmt.executeQuery("select user from dual"); while(rs.next()) user = rs.getString(1); if (conn != null) conn.close(); } catch (SQLException eSQL) { if (recursiveCalls > 0 && !((ValidConnection) conn).isValid()) { System.out.println("\nConnection is no longer valid: " + eSQL); //Calling setInvalid often leads to an exception //so it's a wise idea to put it in a separate try block try { ((ValidConnection) conn).setInvalid(); } catch (SQLException conEx) { System.out.println("\nInvalidating failed: " + conEx); } conn.close(); conn = null; System.out.println("\nRetrying to obtain a new connection"); //making a recursive call to getUser in an attempt to obtain a valid connection //the number of recursive calls allowed is reduced by 1 user = getUser(pds, recursiveCalls - 1); } else { System.out.println("\nSQLException: " + eSQL); } } finally { return user; } }}
在本示例中,getUser 方法在同一方法中实现的 try/catch 语句的 catch 子句中调用其自身。在这里,您将限定允许的递归调用次数为 3。如果连续三次未获取有效连接,您将停止尝试并退出。
除以上讨论的通用高可用性和高性能特性外,UCP for JDBC 还可以与 Oracle RAC 特性(如,快速连接故障切换 (FCF) 和运行时连接负载平衡)集成,从而可以更轻松地管理到 Oracle RAC 数据库的连接。
以下代码段将演示在使用 UCP JDBC 连接池管理到 Oracle RAC 数据库的连接时如何启用 FCF。请注意,要使用 FCF,您需要将 Oracle 通知服务库 (ons.jar) 添加到应用程序的类路径。从 Oracle 数据库 10g 开始,Oracle 通知服务库就已成为 Oracle 数据库的一部分。
... //Creating a pool-enabled data source PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); //Setting pool properties pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); //Setting a RAC-specific URL pds.setURL( "jdbc:oracle:thin:@" + "(DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=ON)" + "(ADDRESS=(PROTOCOL=TCP)" + "(HOST=rachost1)(PORT=1521))" + "(ADDRESS=(PROTOCOL=TCP)" + "(HOST=rachost2)(PORT=1521)))" + "(CONNECT_DATA=(SERVICE_NAME=orcl)))"); pds.setUser("usr"); pds.setPassword("pswd"); pds.setMinPoolSize(10); pds.setMaxPoolSize(20); //Configuring remote ONS subscription pds.setONSConfiguration("nodes=rachost1:4200,rachost2:4200"); // Enabling Fast Connection Failover pds.setFastConnectionFailoverEnabled(true);
当您设置了连接池并启用了 FCF 后,就可以从池中借用连接并在其上创建查询(像在非 RAC 特定程序上一样)。
Connection conn = pds.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = null;
然后您可以实现重试机制,该机制将在 RAC-down 事件触发 UCP FCF 操作后检查连接的有效性,再次尝试到正常 RAC 实例的连接以防连接失效。以下代码段将演示如何在 while 循环中实现该操作。
boolean retry = true; while(retry) { try { //Getting a RAC connection from the pool conn = pds.getConnection(); // Executing a query on the connection. rs = stmt.executeQuery("select user from dual"); rs.next(); System.out.println("\nConnected as : " + rs.getString(1)); //Setting retry to false to exit the loop retry = false; } catch (SQLException eSQL) { System.out.println("\nSQLException: " + eSQL); // Checking connection usability after a RAC-down event triggers UCP FCF actions if (conn == null || !((ValidConnection) conn).isValid()) { //Closing the connection try { conn.close(); } catch (SQLException eClose) { System.out.println("\nException arose when closing connection: " + eClose); } //Setting retry to true to try again retry = true; } } Thread.sleep(1000); }
如果已从该池中成功借用连接并且语句执行未触发异常,则无需重试该操作,执行流将跳出循环。否则,将尝试重新连接并再次执行该语句。
优化连接池
UCP JDBC 连接池提供了一系列属性来帮助您优化池行为。例如,可以规定池的大小,通过设置属性来控制池的初始、最大和最小的池大小。在之前的部分中,您已经了解了如何设置这些属性。
除控制池大小的参数外,还包括控制失效连接的参数。例如,可以设置池的 MaxConnectionReuseTime 属性,而配置一个最大连接重用时间。在某些环境下,您可能会发现在借用一个连接一定次数后将该连接从池中删除会很有用。可以通过设置 MaxConnectionReuseCount 属性来完成此操作。
可以设置 AbandonConnectionTimeout 属性,指示池在连接闲置一段时间后恢复借用连接。还可以设置 TimeToLiveConnectionTimeout 属性,在池恢复该连接前限定该借用连接的使用期限。
如果您认为池将在某一时间出现连接不足,您可以设置 ConnectionWaitTimeout 属性,设置池中无可用连接时应用程序请求将等待的秒数。此外,还有 InactiveConnectionTimeout 属性,该属性使您能够指定连接在从池中删除前将保持多久的未借用状态。
还有一个令人感兴趣的属性是 TimeoutCheckInterval,您可以使用该属性设置超时检查时间间隔,控制以上讨论的超时属性的应用频率。默认情况下,该属性设置为 30,表示超时检查循环每 30 秒运行一次。
到目前为止,本节讨论过的所有优化特性都需要您针对某一属性设定相应的值,才能达到您想要的效果。但是,要启用连接采集特性(用于确保池中拥有一定数量的可用连接),您需要使用更复杂的机制。本节余下内容将通过示例向您解释该功能。
假设您按照以下标准设置池的大小属性:
... pds.setInitialPoolSize(10); pds.setMaxPoolSize(20);
initialPoolSize 设置为 10 表示您在初始化连接池时将拥有 10 个连接。接下来,使用以下代码启用连接采集特性,以使池连接可采集:
pds.setConnectionHarvestTriggerCount(5);pds.setConnectionHarvestMaxCount(2);
以上设定的属性将指示池在池中可用连接数量降到五时恢复两个借用连接。我们现在创建一个包含五个连接对象的数组,然后使用它们放置从池中借用的五个连接:
//Creating an array of connection objects Connection[] conn = new Connection[5];
在使用连接填充以上数组前,您需要创建一个回调对象数组,其中每个对象都必须使用一个连接注册。回调对象必须是 ConnectionHarvestingCallback 抽象接口的一个自定义实现实例。本节中稍后将演示一个简单的实现。
使用以下代码,创建一个由五个 CustConnectionHarvestingCallback 对象组成的数组:
//Creating an array of callback objects CustConnectionHarvestingCallback[] callbk = new CustConnectionHarvestingCallback[5];
在下面的循环中,您将从池中借用四个连接并创建四个回调对象,其中每个对象使用一个连接注册:
//Borrowing four connections from the pool for (int i = 0; i < 4; i++) { conn[i] = pds.getConnection(); //Registering the callback object with each connection callbk[i] = new CustConnectionHarvestingCallback(conn[i]); ((HarvestableConnection) conn[i]).registerConnectionHarvestingCallback(callbk[i]); }
出于测试目的,在借用第五个连接触发采集前,可以在某个连接上禁用采集。您可能还记得您已指定在可用连接数降到五时将向池中返回两个借用连接。默认情况下,连接采集特性将采集最先借用的两个连接。因此,本例中将采集 conn[0] 和 conn[1]。但是,通过将 conn[0] 设置为不可采集,您将采集 conn[1] 和 conn[2]。
//Setting conn[0] as nonharvestable ((HarvestableConnection) conn[0]).setConnectionHarvestable(false);
现在,我们通过借用池中第五个连接来触发采集。
//Borrowing the fifth connection to trigger harvesting conn[4] = pds.getConnection(); callbk[4] = new CustConnectionHarvestingCallback(conn[4]); ((HarvestableConnection) conn[4]).registerConnectionHarvestingCallback(callbk[4]);
让我们回顾一下之前本节对超时检查时间间隔的讨论,该时间间隔默认设置为 30。这表示在本例中将不会即时触发采集,而是在 30 秒的间隔内触发。
// Waiting for harvesting to happen Thread.sleep(30000);
为确保一切按计划进行,您可能想要通查连接,以发现哪些连接已关闭并返回到池中:
//Checking connections for (int i = 0; i < 5; i++) { System.out.println("Connection " + i + " returned to the pool - " + conn[i].isClosed()); }
以上代码生成的输出应显示 conn[1] 和 conn[2] 已关闭并返回到池中,而其余三个连接仍然处于借用状态。
最后是如何实现 ConnectionHarvestingCallback 抽象接口使其 cleanup 方法关闭正在采集的连接:
class CustConnectionHarvestingCallback implements ConnectionHarvestingCallback { private Connection conn = null; public CustConnectionHarvestingCallback(Connection conn) { this.conn = conn; } public boolean cleanup() { try { conn.close(); } catch (Exception e) { return false; } return true; } }
以上是 ConnectionHarvestingCallback 抽象接口实现的一个简单示例。在实际应用中,您可能希望使用更复杂的实现。具体来说,您可能需要在 cleanup 方法中实现更复杂的逻辑,例如,在关闭正在采集的连接前,回滚与连接相关的事务。
正如您在本节中了解到的一样,可以使用多个 UCP JDBC 连接池属性来优化池行为。因此,通常比较好的方法是通过对池设置进行实验来找出最适合您应用程序的组合。
语句池
该建议可能看似显而易见,但是语句池在数据密集型应用程序中确实至关重要。Oracle JDBC 驱动程序支持显式和隐式语句缓存,使您能够缓存准备就绪和可调用的语句。隐式缓存不需要您执行任何特殊操作即可向缓存发送或从缓存检索语句 — 当您调用准备就绪或可调用语句的 close 方法后,该语句将自动进入缓存。当您下次在同一连接上创建该语句时,将从缓存中检索该语句而不是重新创建。如果隐式缓存已开启,那么满足以下条件时,将从缓存中重用语句对象:
- 该语句中使用的 SQL 字符串与保存在缓存中的相同。
- 语句类型也相同,即,准备就绪或者可调用。
- 语句生成的结果集的可滚动类型也相同,即,只能正向传输或可滚动的。
虽然 Oracle JDBC 驱动程序设计的前提是假设隐式缓存已启用,但是默认情况下该特性是未开启的。要启用连接上的隐式缓存,您可以将相关 OracleConnection 对象上的 implicitCachingEnabled 属性设置为 true 并将 statementCacheSize 属性设置为一个正整数。这可以通过以下操作完成:
conn.setImplicitCachingEnabled(true); conn.setStatementCacheSize(10);
使用 UCP JDBC 连接池时,您可以通过将 maxStatements 属性设置为一个正整数来启用语句缓存:
pds.setMaxStatements(10);
如果您执行此操作,将启用池中每个连接的语句缓存。以下程序提供了关于如何在使用连接池的同时使用语句池的一个简单示例:
/**An example of statement pooling in action*/import java.sql.*; import oracle.ucp.jdbc.PoolDataSource;import oracle.jdbc.OracleConnection;import oracle.jdbc.OraclePreparedStatement;import javax.naming.*; import java.util.Hashtable; public class StatementPooling { public static void main(String argv[]) { PoolDataSource pds; //Looking up for the jdbc/HRPool pool-enabled data source registered in the JNDI tree ... //for actual code, see the JNDI lookup example //discussed in the Borrowing a Connection with JNDI section earlier ... try { //Enabling statement caching for the pool's connections pds.setMaxStatements(10); //Borrowing a connection from the pool OracleConnection conn = (OracleConnection) pds.getConnection(); //Checking whether the implicit statement caching is enabled if (conn.getImplicitCachingEnabled()) System.out.println("\nimplicit caching enabled"); else System.out.println("\nimplicit caching disabled"); //Looping through calls to the getRegion private class method that executes a prepared statement for (int i = 1; i < 5; i++ ) { System.out.println("\n" + getRegion(conn, i)); } //Returning the connection to the pool if (conn != null) conn.close(); conn = null; } catch (SQLException eSQL) { System.out.println("Cannot obtain a connection: " + eSQL); } } //This method creates, executes, and then closes a prepared statement private static String getRegion (OracleConnection conn, int region_id ) throws SQLException { OraclePreparedStatement stmt = null; ResultSet rs = null; String region = null; String sql = "SELECT * FROM regions WHERE region_id = ?"; try { stmt = (OraclePreparedStatement)conn.prepareStatement(sql); stmt.setInt(1, region_id); rs = stmt.executeQuery(); rs.next(); region = rs.getString("REGION_NAME"); } catch (SQLException eSQL) { System.out.println("\nSQLException: " + eSQL); } //this code is executed under all circumstances finally { if (rs != null) rs.close (); if (stmt != null) //if implicit caching is enabled, the statement is not actually closed //but is sent to the cache stmt.close (); return region; } }}
如您所看到的,上述类的 getRegion 方法创建、执行并关闭了一个准备就绪的语句,将查询结果返回到调用代码。在 main 方法中运行的循环将反复调用该方法,从而实现隐式语句缓存。在本示例中,您在调用连接池实例的 setMaxStatements 方法时启用了隐式缓存。所以调用 getRegion 方法中准备就绪语句的 close 方法实际上将缓存语句,而不是将其关闭,从而允许程序在对 getRegion 的第二次以及后续的调用中重用该语句。为确保其正常工作,您可以将以下代码添加到 getRegion 方法中,将其放置在对 OracleConnection 对象的 prepareStatement 方法的调用之后:
... //Checking the creation state of the prepared statement int creationState = stmt.creationState(); switch(creationState) { case 0: System.out.println("\nCreation state: new"); break; case 1: System.out.println("\nCreation state: from the implicit cache"); break; case 2: System.out.println("\nCreation state: from the explicit cache"); break; }...
如果您现在执行该应用程序,您应该看到准备就绪语句的创建状态仅在对 getRegion 的第一次调用中为 new — 所有后续的 getRegion 调用将重用隐式缓存的语句。
能够启用池中每个连接上的每条语句的语句缓存仅仅是个开始,但是您能否或如何选择性地应用该技术,在特定池连接上禁用缓存语句,甚至禁用特定语句的缓存?
如果您还记得本节开始时所进行的讨论,那么您可以使用 OracleConnection implicitCachingEnabled 属性来启用或禁用特定连接上的语句缓存。例如,可以启用池内每个连接的语句缓存,然后在其中某个连接上禁用语句缓存,如下所示:
conn.setImplicitCachingEnabled(false);
至于启用或禁用特定语句上的缓存,您可以使用添加到 Statement 接口的新 JDBC 4.0 方法。例如,要使语句对象可进行池化或不可进行池化,您可以使用其 setPoolable 方法,分别传递 true 或 false。要查看语句对象的当前可池化状态,可以使用该对象的 isPoolable 方法。以下将展示如何防止对特定的准备就绪的语句进行隐式缓存:
if(stmt.isPoolable()) stmt.setPoolable(false);
此处值得注意的是,如果连接上的隐式缓存未开启,则对具有 setPoolable(true) 的特定语句的启用尝试将不会实现预期效果。虽然语句对象的 poolable 属性默认为 true,但是您仍然需要像本节开始阶段所描述的那样,先启用该连接或整个连接池的隐式缓存。
结论
在本文中,您了解了如何借助出色的 Oracle 特有 JDBC 特性以及标准的 JDBC 4.0 特性使用连接池和语句池。尤其是了解了 Oracle Universal Connection Pool for JDBC(一个为缓存 JDBC 连接提供连接池实现的新 Oracle 数据库 11g 特性)。然后,您还了解了如何借助 Oracle JDBC 驱动程序特有的特性以及已添加到 Statement 接口的 JDBC 4.0 方法使用语句池。