Oracle Universal Connection Pool(UCP) for JDBC 詳解

dbhelper發表於2015-03-01

瞭解如何利用連線和語句池特性來提高 Oracle 驅動的 JDBC 程式的效能。

作者:Yuli Vasiliev

2009 年 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.jar
ORACLE_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 連線池實際執行的簡單示例,將向您展示如何完成此操作。您將首先建立一個支援池的資料來源例項,然後設定連線和池的屬性。完成後,您將從池中借用一個連線,然後使用該連線與資料庫互動。最後,您將關閉該連線,將其返回到池。

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 程式可以完成此操作:

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 應用程式:

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 介面方法與基於遞迴的重試機制。

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); 

如果您執行此操作,將啟用池中每個連線的語句快取。以下程式提供了關於如何在使用連線池的同時使用語句池的一個簡單示例:

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 方法使用語句池

---&gt>轉載於:http://blog.sina.com.cn/s/blog_5d3da3280100hzyq.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8494287/viewspace-1444339/,如需轉載,請註明出處,否則將追究法律責任。

相關文章