CMP Entity Bean 的 finder 方法

這個主題說明 EJB 2.x CMP Entity Bean 中的查詢支援。對於 EJB 1.1 CMP Entity Bean,WebSphere Application Server 提供了搜尋器支援的延伸。

EJB 2.x 中的查詢支援

EJB 2.x 提供了一種稱為 EJB 查詢語言 (EJB QL) 的搜尋器查詢語法,以及 CMP Entity Bean 的選取方法。finder 方法會從資料庫中取得一或多個 EntityBean 實例,且定義於 Home 介面中。

<query> 元素在部署描述子中用來定義 finder 方法的查詢,它適用於 findByPrimaryKey(key) 之外的每一個 finder 方法。在部署期間,部署描述子所指定的查詢會編譯成 SQL。 查詢的語法包含在 <query> 元素的 <ejb-ql> 元素中:

<query>
<query-method>
<method-name></method-name>
<method-params>
<method-param></method-param>
</method-params>
</query-method>
<result-type-mapping></result-type-mapping>
<ejb-ql></ejb-ql>
</query>

請參閱利用 EJB QL 將 finder 方法新增至 EJB 2.x Bean 中,以取得詳細說明。

如果需要 EJB QL 的詳細資料,請移至 WebSphere Application Server 資訊中心,搜尋關鍵字 "EJB QL"。

EJB 1.x 中的搜尋器支援

重要: EJB 1.1 CMP Entity Bean 的搜尋器支援是 EJB 規格的 WebSphere Application Server 延伸。 只有目標是 WebSphere Application Server 的專案其中的 EJB 1.1 Bean 才會有 EJB 部署描述子編輯器 Bean 頁面中的「搜尋器」區段。

目前所支援且能夠組合使用的 EJB 自訂搜尋器有三種類型:

對於 EJB Home 介面中所定義的每個 finder 方法(不是 findByPrimaryKey 和為了支援關聯而產生的 finder 方法)而言, 必須在 Finder Helper 介面中定義下列查詢子串或宣告之一(在 beanClassNameFinderHelper.java 檔中):

請注意,Home 介面中的搜尋器的傳回類型 java.util.Enumeration 或 java.util.Collection 指出這個搜尋器可傳回多個 Bean。以遠端介面為傳回類型表示會傳回單一 Bean。所有支援的自訂搜尋器類型都是如此。 產生在持續器中的程式碼會處理這個區別。

請務必注意,如果您使用現有的 EJB 1.0 JAR 檔,您可以在 Helper Finder 介面中繼續定義 SQL 查詢字串或方法宣告。 (SQL 查詢字串實際上是說明完整 SELECT 陳述式或只有 WHERE 子句的介面中的一個欄位。) 不過,對於需要您使用 1.0 層次以上的 EJB JAR 檔的任何新開發工作而言,您必須使用 Finder Helper 介面以外的延伸文件來定義您的查詢和方法宣告。 這個主題稍後會有這項討論。

維護不同資料庫的 SQL 相容性

對於 SELECT、WHERE 和方法自訂搜尋器而言,在某些情況中,finder 方法會存取不同的資料庫。 在這個情況下,必須確保能夠維護不同資料庫之間的 SQL 相容性。 比方說,每個資料庫所用的 SQL 語法有可能不同。在這些情況中,請使用 JDBC 所定義的 SQL 延伸,以解決資料庫的差異。

比方說,假設您在開發 CMP Entity Bean,且它需要含有時間戳記/日期欄位的 finder 方法。 另外,也假設這個 Bean 要部署在 DB2® 和 Oracle 資料庫中。問題是 DB2 和 Oracle 的時間戳記/日期欄位的格式不同,這會造成在定義 DB2 和 Oracle 兩者要用的 WHERE 子句時發生困難。 這個特定問題的解決方案是使用 SQL ESC 序列。

請參閱下列各節,以取得 SELECT、WHERE 和方法自訂搜尋器的其他相關資訊。

附註:當使用自訂搜尋器時,請勿傳遞 NULL。

SELECT 自訂搜尋器

SELECT 自訂搜尋器用來在 Finder Helper 介面中輸入整個 select 陳述式,以定義 SQL 查詢。

支援使用 SELECT 自訂搜尋器是為了與較早的版次相容。 在這個版次和未來的版次中,最好不要用 SELECT 自訂搜尋器。

WHERE 自訂搜尋器

專用來在 Finder Helper 介面中輸入過濾用的 WHERE 子句之自訂搜尋器稱為 WHERE 自訂搜尋器。比方說,如果您有一個 VAPGarage CMP Entity Bean 對映至含有名為 CAPACITY 直欄的資料庫表格,Finder Helper 介面就會有如下外觀:

public interface VapGarageBeanFinderHelper { 
   public final static String
      findCapacityGreaterThanWhereClause = 
         "T1.CAPACITY > ?"; 
}

請注意,結果形狀的任何相依關係都會從字串中移除。但仍會有兩個相依關係存在:

  • 直欄名稱 (CAPACITY)
  • 表格別名 (T1)

除非您採取動作變更它,否則直欄名稱不會改變。

除非在對映中新增或移除表格,否則,各代的表格別名都是相同的。在單一表格的情況中,這不見得很重要(別名永遠是 T1)。但在使用多個表格時,就非常重要了。

自行撰寫的 SQL 程式碼中的任何表格參照都必須符合 genericFindSqlString 欄位所設定的表格別名。 這會宣告在 Enterprise Bean 的產生的持續器中。

如同在 SELECT 自訂形式中,finder 參數的數目必須符合 WHERE 子句中的注入點(? 字元)數目。另外,也如同在 SELECT 形式中,必須利用參數類型來判斷將利用哪個 java.sql.PreparedStatement 設定呼叫來注入每個參數。 參數類型必須與直欄類型相容。 如果類型是物件類型,且參數是空值,就會使用 setNull 呼叫。

比方說,Home 介面可包含下列方法:

public java.util.Enumeration findGreaterThan (int threshold) throws
   java.rmi.RemoteException, javax.ejb.FinderException;

在這個 Finder Helper 介面中,WHERE 自訂搜尋器是您能夠提供的形式之一。 比方說(換行是為了發佈):

public static final String findGreaterThanWhereClause =
    "T1.VALUE > ?";

不過,請注意,如果您有 SQL 陳述式沒有包含 WHERE 子句,例如 SELECT * FROM MYTABLE,您應該使用永遠會得出 true 的查詢字串。例如:

public static final String findALLWhereClause = "1 = 1";
方法自訂搜尋器

您利用它將方法簽章輸入 Finder Helper 介面的自訂搜尋器稱為方法自訂搜尋器。它是最靈活的自訂搜尋器類型,但您的工作會增加。 在前面的相同汽車範例中,Finder Helper 介面的外觀如下:

public interface VapGarageBeanFinderHelper { 
    public java.sql.PreparedStatement findCapacityGreaterThan(int threshold)
       throws Exception;}

不過,這不同於 SELECT 和 WHERE 的形式,方法自訂搜尋器需要不只這個。 這個方法需要實作。 您要在符合下列規則的類別中提供您的方法實作:

  • 類別名稱是 beanClassNameFinderObject(在這個範例中,是 VapGarageBeanFinderObject),在 Bean 類別的相同套件中。
  • 類別必須繼承 com.ibm.vap.finders.VapEJSJDBCFinderObject,且必須實作 Bean 的 Finder Helper 介面。
  • 自行撰寫的 SQL 程式碼中的任何表格參照都必須符合 genericFindSqlString 欄位所設定的表格別名。 這會宣告在 Enterprise Bean 的產生的持續器中。

如果要完成我們的範例,Finder 物件會有如下外觀。

/** 
* 實作 VapGarageBeanFinderHelper
* 中之方法的類別。*/ 
public class VapGarageBeanFinderObject extends
      com.ibm.vap.finders.VapEJSJDBCFinderObject implements
   VapGarageBeanFinderHelper { 

   public java.sql.PreparedStatement 
   findCapacityGreaterThan(int threshold) 
   throws Exception { 

      PreparedStatement ps = null; 
      int mergeCount = getMergedWhereCount(); 
      int columnCount = 1; 
      ps = getMergedPreparedStatement("T1.CAPACITY > ?"); 
      for (int i=0; i<(columnCount*mergeCount); i=i+columnCount) { 
          ps.setInt(i+1, threshold); 
      } 
         return ps;
   }
}

在任何方法自訂搜尋器的情況中,產生的持續器會利用您的實作來建立要執行的 PreparedStatement。 這個持續器會執行 PreparedStatement 和處理結果。 實作需要持續器的協助,才能確定查詢的結果集有正確的形狀。 com.ibm.vap.finders.VapEJSJDBCFinderObject 基礎類別提供若干重要的 helper 方法,上面的範例顯示了其中的一部分。 下表列出和說明一組完整的 helper 方法:

方法 說明
getMergedPreparedStatement 使用 WHERE 子句,傳回 WHERE 子句合併在適當位置的 PreparedStatement。 PreparedStatement 會有正確的結果集形狀。
getMergedWhereCount 傳回 WHERE 子句合併在 PreparedStatement 中的次數。 這是必要的,如此才能知道將查詢參數注入 PreparedStatement 的次數。
getPreparedStatement 使用完整查詢字串,並傳回 PreparedStatement。 如果您因故需要執行自己的 WHERE 子句合併,就可以使用這個。 這個情況應該很少。 提供下兩個函數用來協助這些極端的情況。
getGenericFindSqlString 傳回 WHERE 子句合併其中的查詢字串。
getGenericFindInsertPoints 傳回一個整數陣列,這個陣列定義在 getGenericFindSqlString 傳回的查詢字串中,用來合併 WHERE 子句的一或多個點。陣列中的第一個點是字串中的最後一個點。通常最好從查詢子串尾端開始合併,因為在尾端合併不會變更字串中較前面的合併位置。陣列的大小與 getMergedWhereCount 傳回的值相同。

這是非常簡單的情況,由 WHERE 自訂搜尋器來處理會比較好。WHERE 自訂搜尋器可能無法處理較複雜的範例。 比方說,假設您要 finder 取出較複雜的物件,並將它注入 WHERE 子句的多個直欄中。您會結束於外觀如下的 finder 方法:

public java.sql.PreparedStatement 
   findWithComplexObject(BigObject big) throws Exception { 

   PreparedStatement ps = null; 
   int mergeCount = getMergedWhereCount(); 
   int columnCount = 3; 
   int anInt = big.getAnInt(); 
   String aString = big.getAString(); 
      String aLongAsString =
            com.ibm.vap.converters.VapStringToLongConverter.
      singleton().dataFrom(big.getLongObject()); 
  
   ps = getMergedPreparedStatement("(T1.ANINT > ?) AND
        (T1.ASTRING = ?) AND (T2.ALONGSTR <  ?)"); 
   for (int i=0; i<(columnCount*mergeCount); i=i+columnCount) { 
       ps.setInt(i+1, anInt); 
       if (aString == null) 
                    ps.setNull(1, java.sql.Types.VARCHAR);
                else
          ps.setString(i+2, aString); 
       if (aLongAsString == null) 
                    ps.setNull(1, java.sql.Types.VARCHAR);
                else
          ps.setString(i+3, aLongAsString); 
   } 
      return ps;
}

另外,還可能有更複雜的範例。 比方說,所傳遞的物件可能不但包含資料,也包含 WHERE 子句(或如何建立它的指示)。或者,也可能有多個參數,每個參數代表 WHERE 子句中的不同條件。

範例:複式方法自訂搜尋器

雖然關聯不是利用 EJB 部署工具來建立的,但下列範例是如何利用複式方法自訂搜尋器來完成多對多關聯的邏輯表示法。 範例包含 Product 和 Customer Bean 之間的多對多關聯,使用一個中間 Bean (ProdCustLink) 和兩個 1:m 關聯:


複雜的方法自訂搜尋器和 兩個 Bean 之間的多對多關聯之圖形表示法。

您可以撰寫方法自訂搜尋器,只利用單一方法呼叫使關係以任一方向延伸。在本例中,只考慮一個方向:Customer 中的 finder, 其用來擷取和給定產品序號相關聯的所有 Customer 實例。

Customer 的 Home 介面依照下列方式包含適當的方法簽章:

java.util.Enumeration
      findCustomersByProduct(prod.cust.code.ProductKey inKey) 
      throws java.rmi.RemoteException, javax.ejb.FinderException;

Customer 的 Finder Helper 介面包含對應的 finder 方法的簽章:

public java.sql.PreparedStatement 
      findCustomersByProduct(prod.cust.code.ProductKey inKey) 
      throws Exception;

Finder 物件 (CustomerBeanFinderObject) 會建置和快取 finder 的查詢字串,以及實作 finder 方法。

public class CustomerBeanFinderObject 
   extends com.ibm.vap.finders.VapEJSJDBCFinderObject 
      implements CustomerBeanFinderHelper {

      private String cachedFindCustomersByProductQueryString = null;
   .
   .
   .
}

查詢字串欄位的存取元方法利用在 finder 物件中的智慧型起始設定來建置查詢字串,首先是將 WHERE 條件合併在查詢範本中,之後,在 FROM 子句中加入一個參照來指向中間表格。

存取元方法的前半段利用 genericFindInsertPoints 陣列來尋找和更新每個 WHERE 子句。 之後,方法的後半段從每個 FROM 子句的開頭計算轉遞,依照需要在查詢字串中插入指向中間表格的參照,以及更新查詢字串欄位。

protected String getFindCustomersByProductQueryString() {
      if (cachedFindCustomersByProductQueryString == null) {
   
   // 先執行 WHERE
      // 因此,genericFindInsertPoints 是正確的。            int i;
            int[] genericFindInsertPoints = getGenericFindInsertPoints();
            StringBuffer sb = new StringBuffer(getGenericFindSqlString());
            for (i = 0; i < genericFindInsertPoints.length; i++) {
         sb.insert(genericFindInsertPoints[i], 
                        "(T1.id = T2.Customer_id) AND (T2.Product_id = ?)");
}

      // 確定更新每個 FROM 子句。
            String soFar = sb.toString();
            int fromOffset = soFar.indexOf(" FROM ");
            while (fromOffset != -1) {
                  sb.insert((fromOffset+5)," ProdCustLink T2, ");
                  soFar = sb.toString();
                  fromOffset = soFar.indexOf(" FROM ", (fromOffset+5));		
      }
            cachedFindCustomersByProductQueryString = sb.toString();
   }
      return cachedFindCustomersByProductQueryString;
}

在這個方法呼叫之後,查詢字串會有類似下列中的外觀:

SELECT <columns> FROM ProdCustLink T2, CUSTOMER T1 
        WHERE((T1.id = T2.Customer_id) AND (T2.Product_id = ?))

另外,在 Finder 物件中,實作的 finder 會利用查詢字串來建立 PreparedStatement。 最後但不是最不重要的,是在疊代迴圈中利用 Super 類別方法 getMergedWhereCount() 將產品 ID 值新增在每個 WHERE 子句中。

public java.sql.PreparedStatement 
   findCustomersByProduct(ProductKey inKey) 
      throws java.lang.Exception {

      // 取得完整查詢字串,建立 PreparedStatement。
   java.sql.PreparedStatement ps = 
            getPreparedStatement(getFindCustomersByProductQueryString());

      // 將產品 ID 參數注入每個合併的 WHERE 子句中。   for (int i = 0; i > getMergedWhereCount(); i++) {
            if (inKey != null)
                  ps.setInt(i+1, inKey.id);
               else
	 ps.setNull(i+1, 4);
   }

      return ps;
}
相關工作
利用 EJB QL 將 finder 方法新增至 EJB 2.x Bean 中
使用條款 | 讀者意見
(C) Copyright IBM Corporation 2000, 2005. All Rights Reserved.