J2EE 组件开发:实体EJB(下)

提纲:

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

一、客户端接口

  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 部署和运行

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

正文:

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

<ccid_nobr><strong>一、客户端接口 </strong></ccid_nobr>

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

<ccid_nobr><strong>1.1 Remote接口 </strong></ccid_nobr>

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

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

<ccid_nobr></ccid_nobr>



所有分布式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继承的方法可供调用。

<ccid_nobr><strong>1.2 Home接口 </strong></ccid_nobr>

实体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程序的调用。

<ccid_nobr></ccid_nobr>



客户程序查找实体Bean Home对象的方式与查找会话Bean Home对象的方式相同。无论客户程序在J2EE容器内运行,还是在容器之外作为独立的程序运行,都要用到JNDI。如果客户程序在J2EE容器内运行,可以用<ejb-ref>元素引用EJB Home接口。如果实体Bean作为其他EJB的客户程序,则可以在ejb-jar.xml文件的<entity>元素内定义<ejb-ref>元素。 <br><br>实体Bean的Home接口上定义了一个或者多个create(...)方法,这些方法代表着创建实体Bean对应的数据单元的各种方式。对于实体Bean类中的每一个ejbCreate(...)方法,Home接口中必须定义一个create(...)方法。create(...)方法可以包含零个或者多个输入参数,与对应的ejbCreate(...)方法需要的初始化参数类型匹配,但这些create(...)方法都返回一个实体Bean远程接口的实例(例如MyEntityEJB)。此外,create(...)方法还必须能够抛出java.rmi.RemoteException异常和javax.ejb.CreateException异常。 <br><br>create()方法不是唯一可以在实体Bean Home接口上定义的应用层方法。Home接口还可以定义一组findXXX(...)方法,客户程序通过这组方法查询实体Bean。对于实体Bean类定义的每一个ejbFindXXX(...)方法,实体Bean Home接口上必须有一个对应的findXXX(...)方法。对于CMP实体Bean,由于Bean类里面不存在显式定义的ejbFindXXX(...)方法,合法的findXXX(...)可以从实体Bean部署描述器确定。实体Bean Home接口上定义的每一个findXXX(...)方法还应该声明可向客户程序抛出RemoteException异常和FinderException异常。 <br><br>实体Bean必须定义一个ejbFindByPrimaryKey()方法,所以Home接口也至少必须定义一个findByPrimaryKey()方法。findByPrimaryKey()必须返回一个实体Bean远程对象的句柄(例如MyEntityEJB)。容器负责把实体Bean方法ejbFindByPrimarykey()返回的主键关联到具体的Bean实例以及返回的实体Bean客户端远程接口Stub程序。其他findXXX(...)方法的定义也必须与对应的ejbFindXXX(...)方法匹配,两者必须有相同的输入输出参数。客户端的区别在于,从findXXX(...)方法返回的Enumeration或Collection对象包含实体Bean远程对象的实现;而在服务器端的实体Bean组件上,对应的ejbFindXXX(...)方法返回的集合包含的是主键。 <br><br><ccid_nobr><strong>二、实例 </strong></ccid_nobr><br><br>本例是一个Bean管理持久化的实体Bean,这是一个描述工资等级信息的简单Bean。实体Bean的状态信息保存在关系数据库的PayTable表。PayTable表的结构如下: <br><br><ccid_nobr></ccid_nobr></ejb-ref></entity></ejb-ref>


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



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

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

<ccid_nobr><strong>2.1 BMP实体Bean </strong></ccid_nobr>

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

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



<ccid_nobr><font size="4"><font color="#a52a2a"><strong>2.1.1 ejbCreate()方法 </strong></font></font></ccid_nobr>

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

<ccid_nobr></ccid_nobr>

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



代码如下:



<ccid_nobr></ccid_nobr>


publicStringejbCreate(StringempName,floatpayRate)throwsCreateException{

if(empName==null){
thrownewCreateException("雇员姓名是必需的.");
}
try{
StringsqlStmt="INSERTINTOPayTableVALUES(?,?)";
con=ds.getConnection();
PreparedStatementstmt=con.prepareStatement(sqlStmt);
stmt.setString(1,empName);
stmt.setFloat(2,payRate);
stmt.executeUpdate();
stmt.close();
}catch(SQLExceptionsqle){
thrownewEJBException(sqle);
}finally{
try{
if(con!=null){
con.close();
}
}catch(SQLExceptionsqle){}
}
this.empName=empName;
this.payRate=payRate;
returnempName;
}


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

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



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

<ccid_nobr><strong>2.1.2 ejbPostCreate()方法 </strong></ccid_nobr>

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

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

<ccid_nobr></ccid_nobr>

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



<ccid_nobr><font size="4"><font color="#a52a2a"><strong>2.1.3 ejbRemove()方法 </strong></font></font></ccid_nobr>

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

<ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><font color="#a52a2a" size="4">publicvoidejbRemove(){<br> try{<br> StringsqlStmt="DELETEFROMPayTableWHEREempName=?";<br> con=ds.getConnection();<br> PreparedStatementstmt=con.prepareStatement(sqlStmt);<br><br> stmt.setString(1,empName);<br> stmt.executeUpdate();<br> stmt.close();<br><br> }catch(SQLExceptionsqle){<br> thrownewEJBException(sqle);<br> }finally{<br> try{<br> if(con!=null){<br> con.close();<br> }<br> }catch(SQLExceptionsqle){}<br> }<br> } </font></td></tr></tbody></table></ccid_nobr>

<ccid_nobr><font size="4"><font color="#a52a2a"><strong>2.1.4 ejbLoad()和ejbStore()方法 </strong></font></font></ccid_nobr>

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

<ccid_nobr><strong>2.1.5 查找器方法 </strong></ccid_nobr>

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

<ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"> <br><font color="#a52a2a" size="4">publicStringejbFindByPrimaryKey(StringprimaryKey)<br> publicCollectionejbFindInRange(floatlowerLimit,floatupperLimit)<br></font> </td></tr></tbody></table></ccid_nobr>

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

<ccid_nobr></ccid_nobr>


publicCollectionejbFindInRange(floatlowerLimit,floatupperLimit)
throwsFinderException{

try{
StringsqlStmt="SELECTempNamefromPayTable"
+"WHEREpayRateBETWEEN?AND?";
con=ds.getConnection();
PreparedStatementstmt=con.prepareStatement(sqlStmt);

stmt.setFloat(1,lowerLimit);
stmt.setFloat(2,upperLimit);
ResultSetrs=stmt.executeQuery();

ArrayListlist=newArrayList();
while(rs.next()){
Stringid=rs.getString(1);
list.add(id);
}

stmt.close();
returnlist;

}catch(SQLExceptionsqle){
thrownewEJBException(sqle);
}finally{
try{
if(con!=null){
con.close();
}
}catch(SQLExceptionsqle){}
}
}


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

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



<ccid_nobr><font size="4"><font color="#a52a2a"><strong>2.1.6 业务方法 </strong></font></font></ccid_nobr>

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

<ccid_nobr><strong>2.1.7 数据库调用 </strong></ccid_nobr>

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

<ccid_nobr><table style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="550" border="1"><tbody> <tr align="middle" bgcolor="#e6e6e6"> <td><font color="#a52a2a" size="4">方法</font></td> <td><font color="#a52a2a" size="4">对应的SQL调用</font></td> </tr> <tr> <td><font color="#a52a2a" size="4">EjbCreate</font></td> <td><font color="#a52a2a" size="4">INSERT</font></td> </tr> <tr> <td><font color="#a52a2a" size="4">EjbFindByPrimaryKey</font></td> <td><font color="#a52a2a" size="4">SELECT</font></td> </tr> <tr> <td><font color="#a52a2a" size="4">EjbFindInRange</font></td> <td><font color="#a52a2a" size="4">SELECT</font></td> </tr> <tr> <td><font color="#a52a2a" size="4">EjbLoad</font></td> <td><font color="#a52a2a" size="4">SELECT</font></td> </tr> <tr> <td><font color="#a52a2a" size="4">EjbRemove</font></td> <td><font color="#a52a2a" size="4">DELETE</font></td> </tr> <tr> <td><font color="#a52a2a" size="4">EjbStore</font></td> <td><font color="#a52a2a" size="4">UPDATE</font></td> </tr> </tbody></table></ccid_nobr>

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

<ccid_nobr><strong>2.2 Home接口 </strong></ccid_nobr>

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

<ccid_nobr></ccid_nobr>


importjava.util.Collection;
importjava.rmi.RemoteException;
importjavax.ejb.*;

publicinterfacePayHomeextendsEJBHome{

publicPaycreate(StringempName,floatpayRate)
throwsRemoteException,CreateException;
publicPayfindByPrimaryKey(StringprimaryKey)throwsFinderException,
RemoteException;
publicCollectionfindInRange(floatlowerLimit,floatupperLimit)
throwsFinderException,RemoteException;
}


<ccid_nobr><font size="4"><font color="#a52a2a"><strong>2.3 Remote 接口 </strong></font></font></ccid_nobr>

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

<ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"> <br><font color="#a52a2a" size="4">importjavax.ejb.EJBObject;<br> importjava.rmi.RemoteException;<br><br> publicinterfacePayextendsEJBObject{<br> publicvoidsetPayRate(floatpayRate)throwsRemoteException;<br> publicfloatgetPayRate()throwsRemoteException;<br> } </font> </td></tr></tbody></table></ccid_nobr>

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

<ccid_nobr><strong>2.4 部署描述器 </strong></ccid_nobr>

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

<ccid_nobr><strong>2.5 客户程序 </strong></ccid_nobr>

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

<ccid_nobr></ccid_nobr>



<ccid_nobr><font size="4"><font color="#a52a2a"><strong>2.6 部署和运行 </strong></font></font></ccid_nobr>

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

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

<ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"> <br><font color="#a52a2a" size="4">cloudscape -start <br></font> </td></tr></tbody></table></ccid_nobr>

在另一个命令窗口中,执行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。然后,执行下面的命令:

<ccid_nobr></ccid_nobr>

runclient -client PayApp.ear -name PayAppClient -textauth


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

下载本文的代码:<ccid_nobr></ccid_nobr> J2EEEntityEJB2_code.zip

你可能感兴趣的:(数据结构,sql,应用服务器,bean,ejb)