J2EE 组件开发:实体EJB

 

提纲:

======================

一、概述

  1.1 持久化

  1.2 共享访问

  1.3 主键

  1.4 关系

二、实体Bean与JDBC

三、实体EJB组件

  3.1 Bean管理的持久化

  3.2 容器管理的持久化

========================

正文:

========================
一、概述

实体Bean代表着持久性数据存储系统(通常是数据库)中的一个实体。与消息驱动的Bean、会话Bean相比,实体Bean的特点主要表现在持久化(Persistent)、共享访问、拥有主键、关系这四方面。

1.1 持久化

实体Bean的状态信息保存在数据库之类的持久性存储系统中,因此它有持久化的特点。这意味着,即使在应用或J2EE服务器进程的生存周期之外,实体Bean的状态信息仍旧有效。实体Bean的持久化分两种类型:Bean管理的持久化(Bean-Managed Persistent,BMP),或容器管理的持久化(Container-Managed Persistent,CMP)。持久化类型可以在EJB部署描述器中声明。

对于Bean管理的持久化,开发者应当自己编写访问数据库的代码。例如,ejbCreate()执行一个SQL的插入命令,开发者必须自己构造和执行SQL INSERT命令和其他相关的调用。

对于容器管理的持久化,容器自动完成必要的数据库调用。例如,当客户程序请求创建一个实体Bean,容器就生成一个SQL INSERT命令。开发者编写的代码不包含任何SQL调用。容器还能够自动同步实体Bean的状态信息与数据库中保存的数据,这些状态信息通常称为“容器管理的域”(Container-Mananged Field)。容器管理的域也在部署描述器中声明。

容器管理的持久化具有两个显著的优点:首先,容器管理持久化的实体Bean代码量较少;第二,由于Bean不包含任何数据库调用,因此它具有中立于数据库类型的特点。当然,这些优点有时会变成局限,在一些需要高度灵活性的场合,Bean管理的持久化将是首选的方案。例如,当容器不支持CMP实体Bean,或者开发者想要对数据处理方式拥有更多控制权,或者某个数据源过于复杂、难于实现高效的CMP实体Bean映射时,我们应该选用BMP实体Bean。

1.2 共享访问

实体Bean可以由多个客户程序共享。由于多个客户程序可能修改同一个数据,因此,为实体Bean提供事务(Transaction)支持就很重要。一般地,事务管理机制由EJB容器提供,开发者无需在Bean里面设置事务的界限。事务属性可以在Bean的部署描述器中指定。

1.3 主键

每一个实体Bean有一个唯一的对象标识符。例如,一个Customer实体Bean可以通过客户编号识别。实体Bean的唯一标识符也称为主键(Primary Key),它使得客户程序能够定位特定的实体Bean实例。当EJB客户程序通过实体Bean的接口创建或寻找特定的数据库记录,容器把一个主键关联到返回给客户程序的EJB实体Bean句柄。例如,如果在Customer实体Bean里面,客户的名称是主键,则对于容器来说,这个名称应该唯一地标识出数据库里面Customer表的一个记录;从客户程序的角度来看,它映射到了一个唯一的实体Bean实例。

1.4 关系

正如关系数据库中的表,实体Bean之间也可以建立关系。关系的实现方式根据持久化类型的不同而不同。对于Bean管理的持久化,关系需要开发者编写代码实现;对于容器管理的持久化,关系由容器负责实现。由于这个原因,对于容器管理持久化的实体Bean,关系通常被称为容器管理的关系(Container-Managed Relationship)。

根据实体Bean的特点,推荐使用实体Bean的场合包括:

当Bean侧重于描述业务实体而不是业务过程时。

当Bean的状态信息需要持久化时。

二、实体Bean与JDBC

连接和访问数据库是绝大多数J2EE应用的基本要求,J2EE通过JDBC提供这方面的支持。一些厂商(如Oracle)以及下一版本的J2EE规范将支持通过SQLJ访问数据库,但本文仍以JDBC为基础介绍数据库访问。J2EE平台提供对JDBC API的支持,允许我们方便地通过XML形式的部署描述器配置JDBC资源,通过JNDI连接JDBC资源。

JDBC驱动程序的配置、数据源的声明通过XML格式的J2EE模块部署描述器完成。会话Bean和实体Bean都可以在J2EE部署描述器中定义 元素,每个EJB可以配置零个或多个JDBC资源。

例如,下面的例子显示了如何在ejb-jar.xml文件中为实体EJB配置JDBC资源:

<ejb-jar>
  ...
  <enterprise-beans>
    <entity>
      ...
      <resource-ref>
        <res-ref-name>jdbc/Cloudscape</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
      </resource-ref>
    </entity>
  </enterprise-beans>
  ...
</ejb-jar> 


经过上述配置之后,在实体EJB之内利用数据资源就变得非常简单,只需在EJB之内查找命名后的数据源,然后获得一个javax.sql.DataSource对象的句柄。利用DataSource对象可以得到一个java.sql.Connection对象。怎样把Connection对象分配给Bean由容器决定,但一般地,容器会设立一个数据库连接池。接下来,Bean可以按照通常所做的那样,利用Connection对象构造JDBC数据库操作命令,提取结果集。

如果实体Bean的持久化由Bean管理,它必须自己管理数据库连接。虽然本文只通过实体EJB访问JDBC资源,但JDBC资源还可以从会话Bean访问。如果要在会话Bean之内访问JDBC资源,在EJB部署描述器 元素的 子元素之内,通过一个或者多个 元素进行配置。

三、实体EJB组件

实体Bean是描述数据库数据逻辑单元的Java类,实体Bean类定义的域映射到数据库模式定义的构成元素。最简单的情形下,实体Bean的域直接映射到数据库表的列,实体Bean本身就相当于一个数据库的表,实体Bean之间的关系与表之间的关系相似。视图、表的连接结果也可以看作实体Bean,此时这些数据资源的构成元素对应着实体Bean类的域。

如果实体Bean实例的域包含有效数据,则该实例对应着数据源里面的一个具体的数据单元,或者说,一个实体Bean的实例对应着表里面一个特定的记录,或者对应着表连接查询的一个结果。

3.1 Bean管理的持久化

构造BMP实体Bean的许多概念和技术同样适用于CMP实体Bean。然而,正如我们下面将看到的,BMP实体Bean要求手工编写大量的代码。在某些情形下,编写BMP实体Bean和从头开始编写基于JDBC的组件相差无几,差别仅在于JDBC逻辑的封装方式,特别地,BMP实体Bean上的标准方法暗示了将要创建和处理哪些JDBC语句。

图一显示了构造BMP实体Bean所涉及的基本体系结构。



公用的、非最终的、非抽象的实体Bean,比如图一显示的MyBeanManagedEntityEJBean,必须实现javax.ejb.EntityBean接口。EntityBean接口从EnterpriseBean接口扩展得到。EntityBean接口定义了一组实体Bean必须实现的操作,这组操作是EJB容器管理Bean的生命周期所必需的。当然,Bean管理持久化的实体Bean还可以实现业务方法,比如图一显示的someMethod()和anotherMethod()方法。一般地,Bean管理持久化的实体Bean还要以域变量的形式定义一些状态信息,在这些状态信息中包含数据库记录值的缓冲版本。最后一点是,实体Bean必须有一个公用的、没有参数的构造函数,而且不应该实现finalize()方法。

javax.ejb.EntityContext接口由容器实现,它提供了一个指向实体Bean运行时上下文的句柄。EntityContext从更一般化的EJBContext接口扩展得到,并定义了一个获取远程EJB接口句柄的方法,以及一个获取Bean实例主键的方法。在一个实现EntityBean接口的类中,容器构造好Bean的实例之后,就会立即调用Bean的setEntityContext()方法。为便于以后使用,Bean应该在一个域中保存这个值。实体Bean还应该实现unsetEntityContext()方法,当实体Bean断开与容器上下文的联系时,容器将调用unsetEntityContext()方法。

当客户程序想要寻找那些与特定数据单元对应的实体Bean的实例时,容器将调用实体Bean上的ejbFindXXX(...)方法之一(也称为查找器方法)。ejbFindXXX(...)方法扮演的角色与数据库操作中SQL SELECT命令的角色相似。在执行这些方法时,如果出现了应用层错误,BMP实体Bean一般应该抛出一个FinderException异常。

所有的实体Bean都必须定义ejbFindByPrimaryKey()方法。这个方法必须以实体Bean的主键为参数,验证由主键标识的数据单元确实存在于数据库之中,然后把主键返回给容器。如果指定的数据单元不存在,BMP实体Bean应该抛出ObjectNotFoundException异常。ObjectNotFoundException异常是FinderExcetion的子类,表示不能找到指定的目标。一般地,BMP实体Bean在ejbFindByPrimaryKey()方法中构造一个SELECT命令,通过JDBC查询数据库,以此验证带有指定主键的数据确实存在于数据库之中。

除了ejbFindByPrimaryKey()方法之外,BMP实体Bean还可以定义多个附加的ejbFindXXX(...)方法,这些方法带有零个或者多个与业务有关的输入参数,根据这些参数的要求在数据库中查找符合条件的数据单元。对于每一组找到的数据单元,ejbFindXXX(...)方法必须返回与这些数据单元对应的主键的集合。在实现这些方法时,我们可以利用JDBC API执行SQL SELECT命令,然后利用结果集构造出相关主键对象的集合。如果查询返回的结果集中不包含主键,则返回值应该是一个空的集合。

实体Bean可以定义多个ejbCreate()方法,也可以没有ejbCreate()方法;这些ejbCreate()方法可以带多个参数,也可以不带参数。从功能来看,这些可选的ejbCreate()方法与数据库操作中的INSERT命令类似:在当前实体Bean类型对应的数据源中插入一个数据单元,用实体Bean获得的各种数据填写这个数据单元。实体Bean可以通过构造Bean实例期间对域变量的初始化获得这些数据,另外,也可以使用所有作为ejbCreate(...)方法参数传入的数据。容器根据客户程序的请求调用实体Bean的ejbCreate(...)方法,ejbCreate()方法返回的对象代表着新创建实体Bean的主键。

BMP实体Bean必须实现必要的数据库连接逻辑,以便ejbCreate()方法执行数据库的SQL INSERT命令。完成这一任务的基本工具是JDBC API,我们可以通过XML形式的数据源配置选项对它进行配置。如果由于某种原因不能创建实体Bean,BMP实体Bean可以抛出CreateException异常。如果不能创建实体Bean是由于已经存在具有相同主键的数据单元,则BMP实体Bean可以抛出CreateException的子类DuplicateKeyException异常。

对应于每一个ejbCreate(...)方法,实体Bean必须定义一个有着相同输入参数的ejbPostCreate()方法。当容器完成ejbCreate()方法调用,且把实体Bean的实例与客户端引用关联起来之后,接下来它就会调用ejbPostCreate()方法。因此,所有需要在ejbCreate()方法执行完毕之后进行的初始化操作,都可以在ejbPostCreate()方法之内完成。如果ejbPostCreate(...)方法执行时出现了应用层的错误,它也可以抛出CreateException异常。

当客户程序请求拆除实体Bean实例时,容器将调用ejbRemove()方法。实体Bean的ejbRemove()方法把当前关联的数据单元从数据库删除,在功能上类似于SQL的DELETE命令。BMP实体Bean应该使用EntityContext定义的getPrimaryKey()方法获得主键的句柄,然后为该数据单元构造出SQL DELETE命令。如果试图删除数据时出现了错误,ejbRemove()方法可以抛出RemoveException异常。

类似于有状态会话Bean,实体Bean也必须实现ejbPassivate()方法和ejbActive()方法。容器把实体Bean的实例返回给缓冲池之前,会调用ejbPassivate()方法。ejbActivate()方法执行的操作基本上是ejbPassivate()操作的逆向过程,容器根据客户程序对Bean实例的请求,要把以前已经钝化的Bean转入活动状态时,就会调用ejbActive()方法。

ejbPassivate()方法和ejbActive()方法用来关闭和打开非数据源的资源,而ejbLoad()方法和ejbStore()方法用来处理数据库数据。当容器要对Bean里面域的状态和保存在数据库里面的数据进行同步时,它就会调用ejbStore()方法或ejbLoad()方法。

如果容器要用Bean实例里面的数据更新对应的数据库记录,它调用ejbStore()方法。因此,从功能上看,ejbStore()方法相当于执行SQL的UPDATE命令。一般地,当ejbStore()方法被调用时,BMP实体Bean通过JDBC构造和执行SQL UPDATE命令。

如果容器要用数据库里面的最新数据更新实体Bean的状态,它调用ejbLoad()方法。也就是说,ejbLoad()操作更新的是数据单元在内存中的实体Bean实例。通常,ejbLoad()方法首先在关联的EntityContext上调用getPrimaryKey()方法,确定与该实体Bean实例关联的数据单元。然后,Bean通过JDBC查询数据单元的最新数据,并更新Bean的状态。

3.2 容器管理的持久化

如前所述,构造BMP实体Bean是一件比较繁琐的事情,与直接以JDBC为基础编写访问数据库的应用相比,编写BMP实体Bean时手工编写代码的工作量简直不相上下。不过,EJB还提供了一种简化实体Bean编写的办法,这就是容器管理持久化(CMP)的实体Bean。使用CMP实体Bean时,容器为我们实现所有JDBC数据库访问逻辑。也就是说,无论是把数据从实体Bean对象转移到关系数据库,还是从关系数据库转移到实体Bean对象,支持CMP实体Bean的容器担负起了必不可少的“对象到关系”的映射。

下面的说明主要针对开发CMP实体Bean与开发BMP实体Bean的不同之处,不再重复说明CMP和BMP相同的一些概念。

图二显示了构造CMP实体Bean所涉及的基本体系结构。公用的、非最终的、非抽象的CMP实体Bean,比如图二显示的MyContainerManagedEJBean,必须实现EntityBean接口。当然,容器管理持久化的实体Bean还可以实现业务方法,比如图二显示的someMethod()和anotherMethod()方法。另外还要注意的是,CMP实体Bean也必须有一个公用的、不带参数的构造函数,且不应该实现finalize()方法。



应该特别注意的是,CMP必须把全部由容器管理的域定义成public,并确保相关的类型是可串行化的(Serializable)。由容器管理的域直接映射到与CMP实体Bean关联的数据单元的元素,例如,一个容器管理的域可以直接映射到数据库表的一个列。容器通过EJB部署描述器中的定义获知哪些域将由容器管理。

CMP实体Bean的主键可以指定为Bean的一个域,也可以先定义一个独立的包含一个或者多个公用域名字的类(这些公用域名字与CMP实体Bean的域名字对应),然后通过这个类定义主键。

要设置或取消与CMP实体Bean关联的EntityContext,我们分别使用setEntityContext()方法和unsetEntityContext()方法。正如BMP实体Bean,容器在CMP实体Bean上调用这些方法也出于同样的原因,且调用时机在Bean生命周期中的位置也相同。

CMP实体Bean不再要求开发者手工编写ejbFindXXX(...)方法。对于CMP实体Bean,容器从EJB部署描述器读取相关的配置信息,然后决定如何实现这类方法。因此,对于开发者来说,这些方法的实现过程是完全透明的。这些方法的具体实现机制,可能是由EJB支持平台在CMP实体Bean的子类中完成,也可能是通过一些捕获调用并返回结果的独立部件完成。但不管底层的实现机制如何,CMP实体Bean的开发者不必再关注如何编写代码支持这些ejbFindXXX(...)操作。

对于CMP实体Bean,ejbCreate(...)方法也是可选的,而且Bean开发者无需为了支持这些方法而编写数据库INSERT操作之类的代码。需要开发者保证的是,每一个由容器管理的域都用合适的默认值或传递给ejbCreate(...)方法的参数值正确地初始化。CMP实体Bean必需实现与ejbCreate(...)方法匹配的ejbPostCreate(...)方法。

从数据库删除与CMP实体Bean关联的数据之前,容器调用Bean的ejbRemove()方法。编写ejbRemove()方法时,开发者只需关注那些从数据库删除数据之前必须执行的操作,实际从数据库删除数据的所有代码由容器实现,且容器将在必要的时候抛出适当的EJB异常。

ejbPassivate()方法和ejbActivate()方法必须由CMP实体Bean开发者自己实现,这一点与BMP实体Bean相同。在实现ejbPassivate()方法时,开发者应该清除所有与数据源无关的资源;相应地,在实现ejbActivate()方法时,则应该重新获得这些资源。

对于大多数原来BMP实体Bean在ejbLoad()和ejbStore()方法内实现的操作,CMP实体Bean的容器也将自动处理。在BMP实体Bean中,实际的数据存储操作由ejbStore()方法实现,现在这些操作由容器负责,开发者只负责为所有容器管理的域准备好将要保存到数据库的值,比如获取那些容器管理的域的最新值。类似地,实现ejbLoad()方法时,CMP实体Bean的开发者只负责“传播”各种对Bean域值的修改。当容器完成对容器管理的域值的更新之后,容器调用ejbLoad()方法,ejbLoad()方法应该把所有的改动结果反映到其他与此相关的状态数据。

本文后面将提供一个BMP实体Bean开发的例子。这里我们先来看看CMP实体Bean开发过程中的一些要点。首先,CMP实体Bean类必须定义成public和abstract。同时,CMP实体Bean类必须实现:

  • EntityBean接口。
  • 零个或者多个ejbCreate()方法和ejbPostCreate()方法。
  • 为持久化域和关系域定义get和set访问器方法,这些方法必须定义成abstract。
  • 各种select方法,且这些方法必须定义成abstract。
  • 业务方法。



容器管理持久化的实体Bean可以拥有持久化域和关系域,这些域都是虚的(virtual),因此它们不是作为实例变量直接编写到类里面,而是在部署描述器中指定。容器自动对CMP 实体Bean的持久化域进行数据库存储和提取操作。为支持对这些域的访问,CMP实体Bean必须定义抽象的访问器方法。

访问器方法的名字以get或set开头,后面跟上首字母大写的持久化域或关系域的名字。例如,假设CMP EJB的部署描述器指定了name和salary两个持久化域,则EJB里面必须定义如下访问器方法:

public abstract String getName(); 
public abstract void setName(String name); 

public abstract double getSalary(); 
public abstract void setSalary(double salary);


又如,假设一个运动员可以属于多个组,则一个PlayerEJB实例可以关联到多个TeamEJB实例。为描述这种关系,PlayerEJB在部署描述器中定义了一个关系域teams。在PlayerEJB类中,teams关系域的访问器方法定义如下:


public abstract Collection getTeams(); 
public abstract void setTeams(Collection teams);


CMP实体Bean还可以定义select方法。select方法与查找器方法有一些相同的特点:

  • select方法查询数据库,返回查询结果。
  • 部署描述器为select方法指定EJB QL查询。
  • CMP 实体Bean类不实现select方法,而是由容器负责实现。



然而,select方法又与查找器方法有着明显的区别。例如,select方法不在任何本地接口和远程接口中导出,因此不能由客户程序调用,只能由实体Bean里面实现的方法调用。通常,select方法由业务方法调用。select方法必须按照以下规则声明:

  • 方法的名字必须以ejbSelect为前缀。
  • 方法的访问控制修饰符必须是public。
  • 方法必须定义成abstract。
  • throws子句必须包含javax.ejb.FinderException。



下面是一个声明select方法的例子:


public abstract Collection ejbSelectSports(LocalPlayer player) 
throws FinderException;


下表比较了CMP实体Bean与BMP实体Bean实现上的差异。

项目容器管理的持久化Bean管理的持久化
类定义abstract非抽象
数据库调用由工具生成由开发者编写
持久化状态信息由持久化的虚域(Virtual Field)描述作为实例变量编写
持久化域和关系域的访问器方法(get方法和set方法)必须
findByPrimaryKey方法由容器负责由开发者编写
定制的查找器方法 由容器负责,但开发者必须定义EJB QL查询由开发者编写
select方法由容器负责
ejbCreate()方法的返回值null主键


另外,不论是哪种持久化类型,业务方法的编写规则都是一样的。

 

 

提纲:

===================================

一、客户端接口

  1.1 Remote接口

  1.2 Home接口

二、实例

  2.1 BMP实体Bean

        2.1.1 ejbCreate()方法

    2.1.2 ejbPostCreate()方法

    2.1.3 ejbRemove()方法

    2.1.4 ejbLoad()和ejbStore()方法

    2.1.5 查找器方法

    2.1.6 业务方法

    2.1.7 数据库调用

  2.2 Home接口

  2.3 Remote 接口

  2.4 部署描述器

  2.5 客户程序

  2.6 部署和运行

===================================

正文:

===================================

一、客户端接口

在《J2EE 组件开发:实体EJB(上)》中,我们了解了实体EJB的特点、使用场合以及两种持久化类型。实体Bean与会话Bean相比,两者Home、Remote客户端接口的构造和使用方式相似。事实上,除了要在实体Bean的Home接口定义中增加一种查找器方法之外,剩下的只有语义上的细微差别。下面我们就来看看如何构造实体Bean的这些接口,以及客户程序如何访问这些接口。

1.1 Remote接口

实体Bean的远程接口封装了客户程序看到的实体Bean,其构造方式和会话Bean Remote接口的构造方式相同。实体Bean接口一般包含一些get方法和set方法,这些方法分别用来提取和设置数据;同时,Remote接口还可以包含任意应用层的接口定义。

下面我们来看看如何构造和使用实体Bean的Remote接口。图一显示了构造远程EJB接口所涉及的基本体系结构。



所有分布式EJB组件的远程接口,比如图一显示的MyEntityEJB,必须扩展javax.ejb.EJBObject接口。就象会话Bean一样,实体Bean利用底层的Stub程序、Skeleton程序以及容器提供的管理服务,实现从客户端接口到服务器端EJB组件的分布式的、可管理的访问。实际上,就实体Bean和会话Bean而言,两者Remote接口的真正区别仅在于一些语义上的细微差别。

就象会话Bean拥有远程接口一样,每一个实体Bean组件都要有一个远程EJB接口。这个接口为实体Bean的客户程序提供了分布式的接口,使得客户程序能够调用实体Bean的应用层逻辑。对于每一个实体Bean组件上的分布式应用层方法,比如MyEntityEJBean.someMethod(),EJB客户端远程接口必须定义一个对应的应用层方法,比如MyEntityEJB.someMethod()。分布式特性带来的一个附带的影响是,应用层远程接口中的每一个方法必须声明它能够抛出java.rmi.RemoteException。当然,这一规则仅对服务器端组件上需要提供分布式服务的方法有效。除了EJB远程接口上的应用层方法之外,远程实体Bean对象上还有一组从EJBObject继承的方法可供调用。

1.2 Home接口

实体Bean的客户程序通过Home接口创建、查找或拆除实体Bean。事实上,创建实体Bean导致把一个新的数据单元插入到数据源(例如,把一个新行插入到数据库表)。用来查找实体Bean的接口提供了一种查找数据单元的机制,且返回的结果符合面向对象的风格(实体Bean对象)。拆除一个实体Bean导致从数据库删除对应的数据单元。在这一部分,我们将了解如何创建和利用实体Bean Home接口,如何执行这些基本操作以及其他一些辅助性的操作。

图二显示了构造实体Bean Home接口所涉及的基本体系结构以及客户程序如何使用这些接口。要获得应用层EJB Home接口对象,比如图二显示的MyEntityHome(它必须从标准的javax.ejb.EJBHome接口派生),我们只需利用JNDI查找已命名的Home引用。实体Bean的客户端Stub程序实现了实体Bean实例的应用层Home接口,在服务器端,Skeleton程序和容器负责映射来自Stub程序的调用。



客户程序查找实体Bean Home对象的方式与查找会话Bean Home对象的方式相同。无论客户程序在J2EE容器内运行,还是在容器之外作为独立的程序运行,都要用到JNDI。如果客户程序在J2EE容器内运行,可以用 元素引用EJB Home接口。如果实体Bean作为其他EJB的客户程序,则可以在ejb-jar.xml文件的 元素内定义 元素。

实体Bean的Home接口上定义了一个或者多个create(...)方法,这些方法代表着创建实体Bean对应的数据单元的各种方式。对于实体Bean类中的每一个ejbCreate(...)方法,Home接口中必须定义一个create(...)方法。create(...)方法可以包含零个或者多个输入参数,与对应的ejbCreate(...)方法需要的初始化参数类型匹配,但这些create(...)方法都返回一个实体Bean远程接口的实例(例如MyEntityEJB)。此外,create(...)方法还必须能够抛出java.rmi.RemoteException异常和javax.ejb.CreateException异常。

create()方法不是唯一可以在实体Bean Home接口上定义的应用层方法。Home接口还可以定义一组findXXX(...)方法,客户程序通过这组方法查询实体Bean。对于实体Bean类定义的每一个ejbFindXXX(...)方法,实体Bean Home接口上必须有一个对应的findXXX(...)方法。对于CMP实体Bean,由于Bean类里面不存在显式定义的ejbFindXXX(...)方法,合法的findXXX(...)可以从实体Bean部署描述器确定。实体Bean Home接口上定义的每一个findXXX(...)方法还应该声明可向客户程序抛出RemoteException异常和FinderException异常。

实体Bean必须定义一个ejbFindByPrimaryKey()方法,所以Home接口也至少必须定义一个findByPrimaryKey()方法。findByPrimaryKey()必须返回一个实体Bean远程对象的句柄(例如MyEntityEJB)。容器负责把实体Bean方法ejbFindByPrimarykey()返回的主键关联到具体的Bean实例以及返回的实体Bean客户端远程接口Stub程序。其他findXXX(...)方法的定义也必须与对应的ejbFindXXX(...)方法匹配,两者必须有相同的输入输出参数。客户端的区别在于,从findXXX(...)方法返回的Enumeration或Collection对象包含实体Bean远程对象的实现;而在服务器端的实体Bean组件上,对应的ejbFindXXX(...)方法返回的集合包含的是主键。

二、实例

本例是一个Bean管理持久化的实体Bean,这是一个描述工资等级信息的简单Bean。实体Bean的状态信息保存在关系数据库的PayTable表。PayTable表的结构如下:


CREATE TABLE PayTable (empName VARCHAR(10),
payRate REAL);



其中empName表示雇员姓名,payRate表示工资等级。empName是主键。

实体Bean类、Home接口和Remote接口分别在PayEJB.java、PayHome.java和Pay.java中实现。客户程序是PayClient.java。

2.1 BMP实体Bean

PayEJB实体Bean类符合以下要求:

  • 实现EntityBean接口。
  • Bean类定义成public。
  • Bean类不能定义成抽象的或最终的。
  • 实现零个或者多个ejbCreate()方法和ejbPostCreate()方法。
  • 实现ejbFindXXX(...)方法。
  • 实现业务方法。
  • 不能实现finalize()方法。



2.1.1 ejbCreate()方法

如前所述,当客户程序调用create()方法,EJB容器调用对应的ejbCreate()方法。PayEJB只有一个ejbCreate()方法,它完成如下任务:

  • 把实体Bean的状态信息插入PayTable表。
  • 初始化实例变量。
  • 返回主键。



代码如下:




  public String ejbCreate(String empName, float payRate) throws CreateException {
 
      if (empName == null) {
        throw new CreateException("雇员姓名是必需的.");
      } 
    try {
        String sqlStmt = "INSERT INTO PayTable VALUES ( ? , ? )";
        con = ds.getConnection();
        PreparedStatement stmt = con.prepareStatement(sqlStmt);
        stmt.setString(1, empName);
        stmt.setFloat(2, payRate);
        stmt.executeUpdate();
        stmt.close();
      } catch (SQLException sqle) {
        throw new EJBException(sqle);
      } finally {
         try {
            if (con != null) {
              con.close();
            }
         } catch (SQLException sqle) {}
      }
      this.empName = empName;
      this.payRate = payRate;
      return empName;
    }
 


编写实体Bean的ejbCreate()方法时,应注意以下几点:

  • 访问控制修饰符必须是public。
  • 返回值类型必须是主键(仅对于Bean管理的持久化)。
  • 参数值必须是合法的Java RMI类型。
  • 方法不能有final或static修饰符。



值得指出的是,非J2EE的应用也可以直接把实体Bean的状态信息插入到数据库。例如,可以用SQL命令直接把记录插入到PayTable表。虽然此时对应于该记录的实体Bean并非由ejbCreate()方法创建,但客户程序可以找到这个实体Bean。

2.1.2 ejbPostCreate()方法

EJB容器完成对ejbCreate()方法的调用之后,紧接着调用ejbPostCreate()方法。与ejbCreate()方法不同,ejbPostCreate()方法可以调用EntityContext接口定义的getPrimaryKey()和getEJBObject()方法。ejbPostCreate()方法通常可以是空的,例如PayEJB的ejbPostCreate()就是空的。

ejbPostCreate()方法必须符合以下要求:

  • 参数的数量和类型必须匹配对应的ejbCreate()方法。
  • 访问控制修饰符必须是public。
  • 不能有final和static方法修饰符。
  • 返回值类型必须是void。



2.1.3 ejbRemove()方法

客户程序通过调用remove()方法拆除实体Bean,这个调用导致EJB容器调用ejbRemove()方法,ejbRemove()方法从数据库删除实体Bean的状态信息。PayEJB的ejbRemove()方法如下:

  public void ejbRemove() {
       try {
          String sqlStmt = "DELETE FROM PayTable WHERE empName = ? ";
          con = ds.getConnection();
          PreparedStatement stmt = con.prepareStatement(sqlStmt);
 
          stmt.setString(1, empName);
          stmt.executeUpdate();
          stmt.close();
 
       } catch (SQLException sqle) {
          throw new EJBException(sqle);
      } finally {
         try {
            if (con != null) {
              con.close();
            }
         } catch (SQLException sqle) {}
      }
    }


2.1.4 ejbLoad()和ejbStore()方法

在PayEJB类中,ejbLoad()方法先构造出一个SQL SELECT命令,然后执行SQL命令从数据库读取工资等级信息,把它保存到实例变量。ejbStore()方法先构造出一个SQL UPDATE命令,然后执行该UPDATE命令更新数据库中的数据。请参见PayEJB.java中的实现代码。

2.1.5 查找器方法

PayEJB定义了两个查找器方法:


  public String ejbFindByPrimaryKey(String primaryKey)
    public Collection ejbFindInRange(float lowerLimit, float upperLimit)


第一个方法根据主键进行查找,返回符合条件的雇员;第二个方法根据指定的薪水等级范围进行查找,返回符合条件的雇员的集合。下面给出了第二个方法的实现代码:


  public Collection ejbFindInRange(float lowerLimit, float upperLimit)
         throws FinderException {
 
      try {
          String sqlStmt = "SELECT empName from PayTable " 
                       + "WHERE payRate BETWEEN ? AND ?";
          con = ds.getConnection();
          PreparedStatement stmt = con.prepareStatement(sqlStmt);
 
          stmt.setFloat(1, lowerLimit);
          stmt.setFloat(2, upperLimit);
          ResultSet rs = stmt.executeQuery();
 
          ArrayList list = new ArrayList();
          while (rs.next()) {
            String id = rs.getString(1);
            list.add(id);
          } 

        stmt.close();
        return list;
 
      } catch (SQLException sqle) {
         throw new EJBException(sqle);
      } finally {
         try {
            if (con != null) {
              con.close();
            }
         } catch (SQLException sqle) {}
      }
    }
 


对于BMP实体Bean,查找器方法必须符合以下要求:

  • 必须实现ejbFindByPrimaryKey()方法。
  • 方法的名字必须以ejbFind为前缀。
  • 访问控制修饰符必须是public。
  • 方法的修饰符不能是final或static。
  • 参数和返回值类型必须是合法的Java RMI类型。
  • 返回值类型必须是主键或主键的集合。



2.1.6 业务方法

PayEJB定义了两个简单的业务方法,setPayRate和getPayRate(),分别用来设置和获取薪水等级。

2.1.7 数据库调用

下表总结了PayEJB中各个方法的数据库访问类型:

方法对应的SQL调用
EjbCreateINSERT
EjbFindByPrimaryKeySELECT
EjbFindInRangeSELECT
EjbLoadSELECT
EjbRemoveDELETE
EjbStoreUPDATE


在PayEJB中,业务方法最终通过ejbLoad()方法和ejbStore()方法完成数据库调用,所以上表没有显示出业务方法对应的数据库调用类型。

2.2 Home接口

客户程序通过Home接口创建和寻找实体Bean。PayHome接口的定义如下:


import java.util.Collection;
  import java.rmi.RemoteException;
  import javax.ejb.*;
 
  public interface PayHome extends EJBHome {
 
    public Pay create(String empName, float payRate) 
      throws RemoteException, CreateException;
    public Pay findByPrimaryKey(String primaryKey) throws FinderException, 
       RemoteException;
  public Collection findInRange(float lowerLimit, float upperLimit)
         throws FinderException, RemoteException;
  }


2.3 Remote 接口

Remote接口扩展javax.ejb.EJBObject,定义了可供客户程序调用的业务方法。PayEJB的Remote接口定义如下:


import javax.ejb.EJBObject;
  import java.rmi.RemoteException;
 
  public interface Pay extends EJBObject {
    public void setPayRate(float payRate) throws RemoteException;
    public float getPayRate() throws RemoteException;
  }


Remote接口中的每一个方法必须匹配EJB类中的一个方法,方法的参数和返回值类型必须是合法的RMI类型。另外,方法的throws必须包含java.rmi.RemoteException。

2.4 部署描述器

部署EJB之前,应该先把EJB封装成JAR文件。每一个EJB模块(包含EJB组件的JAR文件)都必须包含一个ejb-jar.xml部署描述器。虽然通常我们可以利用GUI界面的工具,通过填空的方式编写ejb-jar.xml部署描述器,但理解ejb-jar.xml仍是必要的。请参见本文下载代码中的例子。

2.5 客户程序

客户程序PayClient.java先把四个记录插入到PayTable表,然后寻找雇员名字为“孙悟空”的记录,输出其工资等级。接着,客户程序修改该雇员的工资等级。最后,客户程序查找工资等级在5.0到20.0的雇员,输出清单。下面是PayClient.java的运行结果。



2.6 部署和运行

接下来我们看看如何在Sun J2EE SDK和Cloudscape数据库、Windows 2000上运行这个应用。Cloudscape数据库可以随J2EE SDK一起下载。

首先执行j2ee命令启动J2EE 服务器。然后,从命令行启动Cloudscape数据库服务器:


cloudscape -start


在另一个命令窗口中,执行cloudscape -isql进入cloudscape控制台,执行前面给出的SQL CREATE命令创建PayTable表。

在接下来的说明中,我们假定应用已经封装成EAR文件PayApp.ear。关于J2EE应用封装和部署的详细说明,请参见开发平台的相关文档。

启动J2EE SDK的deploytool,选择菜单“File->Open”,打开PayApp.ear文件。接下来,选择菜单“Tools -> Deploy”部署应用。出现部署提示时,选中"Return Client JAR"检查框。

在一个命令窗口中,进入EAR文件(PayAppClient.jar文件)所在目录,把环境变量APPCPATH设置为PayAppClient.jar。然后,执行下面的命令:

runclient -client PayApp.ear -name PayAppClient -textauth


在登录提示中,输入用户名字j2ee,输入密码j2ee。客户程序的输出请参见图三。

下载本文的代码:
J2EEEntityEJB2_code.zip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值