CMP 实体 bean 的 finder 方法

本主题描述了在 EJB 2.x CMP 实体 bean 中支持查询。对于 EJB 1.1 CMP 实体 bean,WebSphere® Application Server 提供 finder 支持的扩展。

EJB 2.x 中的查询支持

EJB 2.x 为 CMP 实体 bean 的 finder 和 select 方法提供被称为 EJB 查询语言(EJB QL)的查询语法。finder 方法从数据库中获取一个或多个实体 bean,并且这些方法是在 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 中的 finder 支持

要点: EJB 1.1 CMP 实体 bean 的 finder 支持是对 EJB 规范的 WebSphere Application Server 扩展。对于目标为 WebSphere Application Server 的项目中的 EJB 1.1 bean,仅显示 EJB 部署描述符编辑器的 Bean 页上的 Finder 部分。

当前支持三种类型的 EJB custom finder,它们可以组合使用:

对于在 EJB home 接口中定义的每个 finder 方法(除 findByPrimaryKey 以及为支持关联而生成的那些 finder 方法之外),必须在 finder helper 接口(在文件 beanClassNameFinderHelper.java 中)中定义下列其中一个查询字符串或声明:

注意,home 接口中的 finder 上的返回类型 java.util.Enumeration 或 java.util.Collection 指示此 finder 可能会返回多个 bean。使用远程接口来作为返回类型指示返回了单个 bean。对于受支持的所有 custom finder 类型,情况也是这样。生成到持久程序中的代码会处理此差异。

注意,如果正在使用现有 EJB 1.0 JAR 文件,则可以继续在 helper finder 接口中定义 SQL 查询字符串或方法声明,这一点很重要。(SQL 查询字符串实际上是描述 FULL SELECT 语句或仅 WHERE 子句的接口上的一个字段。)但是,对于要求您在高于 1.0 的级别使用 EJB JAR 文件的任何新的开发工作,需要您使用扩展文档而不是 finder helper 接口来定义查询和方法声明。这稍后在本主题中讨论。

维护不同数据库间的 SQL 兼容性

对于 SELECT、WHERE 和方法 custom finder,可能存在 finder 方法访问不同数据库的情况。在此情况下,需要确保在不同数据库之间维护了 SQL 兼容性。例如,每个数据库使用的 SQL 语法可能不同。在这些情况下,使用由 JDBC 定义的 SQL 扩展来解决数据库之间的差异。

例如,假定正在开发需要一个 finder 方法的 CMP 实体 bean,而该方法涉及时间戳记/日期字段。同时假定此 bean 将部署到 DB2® 和 Oracle 数据库中。问题是 DB2 和 Oracle 中的时间戳记/日期字段的格式不同,这会在定义一个 WHERE 子句以供 DB2 和 Oracle 同时使用时造成困难。这种特定问题的解决方案是使用 SQL 换码序列。

可以在下列部分中找到关于 SELECT、WHERE 和方法 custom finder 的其他信息。

注意:当使用 custom finder 时,不要传递 NULL。

SELECT custom finder

SELECT custom finder 用来在 finder helper 接口中输入整个 select 语句以定义 SQL 查询。

使用 SELECT custom finder 支持与先前发行版的兼容性。此发行版和将来发行版不鼓励使用 SELECT custom finder。

WHERE custom finder

只将过滤 WHERE 子句输入到 finder helper 接口中的 custom finder 称为 WHERE custom finder。例如,如果有一个被映射至(具有名为 CAPACITY 的列的)数据库表的 VAPGarage CMP 实体 bean,则 finder helper 接口将类似于如下内容:

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

注意,以结果形式出现的任何依赖项都将从字符串中除去。仍存在两个依赖项:

  • 列的名称 (CAPACITY)
  • 表的别名 (T1)

仅当您更改列名时,该列名才会发生更改。

从一代演化到下一代时,表的别名将不变,除非将表添加至映射或从映射除去这些表。如果这些表是单一表的话,这可能没有什么意义(别名始终是 T1)。但在使用多个表时,这非常重要。

手写 SQL 代码中的所有表的引用都必须与 genericFindSqlString 字段中设置的表别名相匹配。这在企业 bean 的生成持久程序中作了声明。

同在 SELECT 定制格式中一样,finder 参数的数目必须与 WHERE 子句中的注入点(? 字符) 数目相匹配。而且,与 SELECT 格式中一样,参数类型将用来确定哪个 java.sql.PreparedStatement set 调用将用于注入每个参数。参数类型必须与列类型相兼容。如果类型是对象类型,而参数为空,将使用 setNull 调用。

例如,home 接口可能包含以下方法:

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

在此 finder helper 接口中,WHERE custom finder 是您可以提供的其中一种格式。例如(断行是为了方便发布):

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

但要注意,如果有不包含 WHERE 子句的 SQL 语句(如 SELECT * FROM MYTABLE),则应使用其值始终为 true 的查询字符串。例 如:

public static final String findALLWhereClause = "1 = 1";
方法 custom finder

在其中将方法特征符输入到 finder helper 接口中的 custom finder 称为方法 custom finder。它是最灵活的 custom finder 类型,但是它需要您进行更多的工作。使用以前相同的车库示例,finder helper 接口将类似于如下内容:

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

然而,与 SELECT 和 WHERE 格式不同,这对于方法 custom finder 来说不够。需要实现此方法才行。您提供了方法在类中的实现方式,它遵循这些规则:

  • 类的名称为 beanClassNameFinderObject (在此示例中,为 VapGarageBeanFinderObject),并且与 bean 类在同一包中。
  • 类必须扩展 com.ibm.vap.finders.VapEJSJDBCFinderObject 并且必须实现 bean 的 finder helper 接口。
  • 手写 SQL 代码中的所有表的引用都必须与 genericFindSqlString 字段中设置的表别名相匹配。这在企业 bean 的生成持久程序中作了声明。

要完成示例,finder 对象将类似如下内容。

/** 
* Implementation class for methods in 
* 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;
   }
}

对于任何方法 custom finder,生成的持久程序都会使用您的实现来创建要执行的 PreparedStatement。持久程序将执行 PreparedStatement 并处理结果。实现需要持久程序的帮助以确保查询的结果集形式正确。com.ibm.vap.finders.VapEJSJDBCFinderObject 基类提供了几种重要的 helper 方法,在以上示例中就显示了某些方法。在下表中,列示并描述了完整的 helper 方法集合:

方法 描述
getMergedPreparedStatement 采用 WHERE 子句并返回已将 WHERE 子句合并到适当位置的 PreparedStatement。PreparedStatement 将具有正确的结果集形式。
getMergedWhereCount 返回 WHERE 子句被合并到 PreparedStatement 中的次数。这需要知道将查询参数注入到 PreparedStatement 中的次数。
getPreparedStatement 采用完整的查询字符串,并返回 PreparedStatement。如果由于某些原因,您需要合并自己的 WHERE 子句,可使用此项。这应是极少情况。提供了下面两个函数以对这些极端事例有所帮助。
getGenericFindSqlString 返回 WHERE 子句合并到其中的查询字符串。
getGenericFindInsertPoints 返回一个整数数组,它定义了 WHERE 子句在 getGenericFindSqlString 返回查询字符串中进行合并的点。数组中的第一个点就是字符串中的最后的点。通常最好的方法就是从查询字符串的末尾进行合并,原因是在末尾处合并将不会改变字符串中较早进行合并的位置。数组的大小与从 getMergedWhereCount 返回的值相同。

这是一种相当简单的情况,WHERE custom finder 就可以很好地处理这种情况。对于更复杂的示例,WHERE custom finder 可能就不够用了。例如,假设您想要一个处理较为复杂的对象的 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 子句的指示信息)。或者,可能有多个参数,每个参数表示 WHERE 子句中的不同条件。

示例:复杂方法 custom finder

虽然未用 EJB 部署工具创建关联,但以下示例仍是可如何使用复杂方法 custom finder 来实现多对多关联的逻辑表示。该示例涉及产品 bean 与客户 bean 之间的多对多关联,其中使用了一个中间 bean(ProdCustLink)和两个 1:m 关联:

复杂方法 custom finder 和两个 bean 之间的多对多关联的图形表示法。

可以编写方法 custom finder 来从任一方向展开与一个方法调用的关系。对于此示例,只考虑一个方向:Customer(客户)中的一个 finder,它检索与给定产品键相关联的所有 Customer 实例。

Customer(客户)的 home 接口包含相应的方法特征符,如下所示:

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

客户的 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 对象中的延迟初始化,查询字符串字段的 accessor 方法通过以下步骤来构建查询字符串:先将 WHERE 条件合并到查询模板中,然后将对中间表的引用添加到 FROM 子句中。

accessor 方法的前半部分使用 genericFindInsertPoints 数组来定位和更新每个 WHERE 子句。然后,该方法的后半部分从每个 FROM 子句开头向前计数,在需要时将对中间表的引用插入到查询字符串中,并更新查询字符串字段。

protected String getFindCustomersByProductQueryString() {
      if (cachedFindCustomersByProductQueryString == null) {
   
   // Do the WHERE first 
      // so that the genericFindInsertPoints are correct.
            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 = ?)");
}

      // Make sure to update every FROM clause.
            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。最后但并非最不重要的一点是,通过在迭代循环中使用超类方法 getMergedWhereCount(),产品标识值被添加到每个 WHERE 子句中。

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

      // Get the full query string and make a PreparedStatement.
   java.sql.PreparedStatement ps = 
            getPreparedStatement(getFindCustomersByProductQueryString());

      // Inject the product id parameter into each merged WHERE clause.
   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

反馈