2008年(3500)
分类:
2008-05-04 19:53:36
在以后的日子里,将由Jackliu向大家陆续提供一系列EJB教程,有学习EJB的朋友请同步参考EJB相关书籍,实战系列将以例程的方式帮助你理解这些基本的概念,其中将包括:
所有章节完毕后将制作成pdf电子文档,供大家下载
实战EJB之五 开发实体BMP(EJB 1.1规范)
前一节介绍了EntityBean的有关介绍,并通过开发、部署实体CMP的例子介绍EJB1.1规范的CMP的有关特性,在这一节中你将了解如下内容:
根据规范中定义的EJB事务持久性(persistence)的特性被分为容器管理持久性(CMP)和Bean管理持久性(BMP)。虽然使用容器管理持久性给编程带来极大的方便,但是将事务持久性交于容器来控制降低了Bean的开发能力;BMP的Bean具有灵活的业务处理能力和更灵活的持久性控制能力,常用来映射一些复杂的数据视图或很难用CMP实现的复杂逻辑处理。
BMP的寿命周期和CMP的寿命周期管理机制是相同的,不同的是BMP的事务持久性管理机制交于Bean开发者,所以,在创建、更新、删除等数据库操作时,两种类型的Bean的顺序图是不一样的。为了说明这一点,可以从CMP和BMP在钝化/激活顺序图中分析,当然Bean的创建、查找、删除也是不同的:
通过图5-1和5-2的比较,我们很容易会发现:
CMP:当一个Bean实例被客户引用,并执行一个业务方法后,容器会自动读取Bean的实例字段(还记得我们在上一节在实现一个CMP时,为Bean定义了映射到数据库字段的Public型类字段吗),然后,通过容器与数据库发生关系,保存改变的数据,执行完毕后Bean被钝化,并调用ejbPassviate()方法通知Bean。当客户过一端时间又调用这个Bean的某业务方法时,被钝化的Bean又重新的激活,但是并不是马上执行这个业务方法,而是由EJB对象首先调用ejbActivate()方法通知Bean,Bean实例要激活,然后从数据库中提取数据,并自动将数据值映射到Bean实例,然后调用ejbLoad()方法,实例被再一次初始化,最后才开始执行要执行的业务方法,红色箭头和红色时间块做了明显的表示。
BMP:当一个Bean实例被客户引用,并执行一个业务方法后,容器会执行Bean的ejbStore()方法,并由这个方法把数据保存到数据库中(下面的例子你将会发现,我们不再为 Bean定义全局类变量,而是定义一些私有类变量),执行完毕后Bean被钝化,并调用ejbPassviate()方法通知Bean。当客户过一端时间又调用这个Bean的某业务方法时,被钝化的Bean又重新的激活,但是并不是马上执行这个业务方法,而是由EJB对象首先调用ejbActivate()方法通知Bean,Bean实例要激活,然后调用Bean的ejbLoad()方法,这个方法负责从数据库中提取数据,Bean实例被初始化,最后才开始执行要执行的业务方法。
<图5-1>
<图5-2>
BMP Bean要求所有的数据库操作都要由Bean实例完成,这些方法基本上包括:
setXXX():因为BMP不在为容器声明public类型的由容器来管理的映射字段,所以setXX方法需要开发者实现
getXXX(): 取得Bean字段值
ejbCreate():在CMP中,由容器实现,并返回一个NULL值,在BMP中必须由开发者自己实现,返回创建记录的主键值
ejbLoad(): 在CMP中,由容器实现,在BMP中必须由开发者自己实现,以实现组件非持久性状态缓存持久性信息
ejbStore():在CMP中,由容器实现,在BMP中必须由开发者自己实现,将信息从组件的非持久性状态转到持久性状态
ejbRemove():在CMP中,由容器实现,在BMP中必须由开发者自己实现
unsetEntityContext():在情境要求被释放时,释放在setEntityContext()中缓存的情境资源和取得的资源
setEntityContext():设置情境资源,初始化数据库连接对象
ejbActivate(): 在CMP中,由容器实现,在BMP中必须由开发者通过情境参数设置主键值
ejbPassivate():在CMP中,由容器实现,在BMP中必须由开发者取消Bean与数据库记录的持久性工作,进入钝化状态
ejbFindByPrimaryKey():在CMP中,由容器实现,在BMP中必须由开发者自己实现
ejbFindXXX():在CMP中,由容器实现,在BMP中必须由开发者自己实现
总体来看,在规范1.1中,CMP和BMP各有千秋,从机制上没有实质的差异,对于客户端的引用是不会察觉到两者的使用差异。不过是一个善于开发,灵活性小,且增加了部署工作(字段映射、编写SQL处理语句);另一个不善于开发,灵活性大,部署工作较少(没有了字段影射等麻烦,但却增加了配置外部引用资源[因为Bean会通过一个JNDI来查找数据库连接池],移植性较CMP差)。关于BMP的寿命周期请参看上一节介绍的EntityBean寿命周期
上一节编写了一个CMP的例子,同样我们可以试者将它改写成一个BMP,假设功能需求不变化(功能介绍参看第四节的相关章节),为这个BMP起名为Bmp1Book 设计一个BMP Bean与CMP同样至少包括四个步骤:
1.开发主接口(Bmp1BookHome.java):
主接口的设计与CMP的主接口设计一样,参照上一节主接口的设计,改动之处用黑体加粗显示。
Bmp1BookHome.java代码:
import java.util.Collection; import java.rmi.RemoteException; import javax.ejb.*; //EJB BMP 1.1实战例子 public interface Bmp1BookHome extends EJBHome{ public Bmp1Book create(String bookid,String bookname,double bookprice) throws RemoteException,CreateException; //按主键[bookid字段]查找对象 public Bmp1Book findByPrimaryKey(String bookid) throws FinderException,RemoteException; //查找定价符合范围内的图书,将结果放到Collection中 public Collection findInPrice(double lowerLimitPrice,double upperLimitPrice) throws FinderException,RemoteException; } |
假设我们保存到D:\ejb\Bmp1Book\src\Bmp1BookHome.java
2.开发组件接口(Bmp1Book.java):
组件接口的设计与CMP的组件接口设计一样,参照上一节组件接口的设计,改动之处用黑体加粗显示。
Bmp1Book.java代码:
|
假设我们保存到D:\ejb\Bmp1Book \src\Bmp1Book .java
3.开发Bean实现类(Bmp1BookEJB.java):
最大的改动是Bean的实现类,这个类里将包括更多SQL实现的细节代码。首先要引用更多的开发包:java.sql.*;javax.sql.*;javax.naming.*; Bean不在声明全局的类变量,类变量的映射改较给Bean来管理。另外,还需要声明一个EntityContext情境变量,我们将通过这个变量的getPrimaryKey()方法得到保存在情境中的主关键字值,以便在Bean在激活时重新初始化Bean数据。因为要对数据库直接操作,所以我们要定义一个DataSource对象,在Bean初始化时从连接池中取得一个有效数数据库对象。定义的Connect对象将在获取一个数据库连接时被引用。dbjndi存放了一个获得数据库资源的JNDI名。改造后的Bmp1BookEJB如下: Bmp1BookEJB.java代码:
|
假设我们保存到D:\ejb\Bmp1Book\src\Bmp1BookEJB .java
到此为止我们的Bean程序组件已经改写完毕了,使用如下命令进行编译:
cd bean\Bmp1Book mkdir classes cd src javac -classpath %CLASSPATH%;../classes -d ../classes *.java |
如果顺利你将可以在..\Bmp1Book\classes目录下发现有三个类文件。
4.编写部署文件:
ejb-jar.xml文件:
|
假设我们保存到D:\ejb\Bmp1Book\classes\META-INF\ejb-jar.xml(注意META-INF必须大写)
现在让我们看看当前的目录结构:
Bmp1Book <文件夹> classes<文件夹> META-INF<文件夹> ejb-jar.xml Bmp1Book .class Bmp1Book EJB.class Bmp1BookHome.class src<文件夹> Bmp1Book.java Bmp1BookEJB.java Bmp1BookHome.java |
在部署之前我们需要将这些类文件和xml文件做成一个jar文件,EJB JAR文件代表一个可被部署的JAR库,在这个库里,包含了服务器代码与EJB模块的配置。ejb-jar.xml文件被放置在JAR文件所指定的META-INF目录中。我们可以使用如下命令得到EJB JAR文件:
cd d:\ejb\Bmp1Book\classes (要保证类文件在这个目录下,且有一个META-INF子目录存放ejb-jar.xml文件) jar -cvf bmp1Book.jar *.* |
确保bmp1Book.jar文件包括的文件目录格式如下:
META-INF<文件夹> ejb-jar.xml InsufficientFundException.class StatefulAccount.class StatefulAccountEJB.class StatefulAccountHome.class |
部署工具一般由Java应用服务器的制造商提供,在这里我使用了Apusic应用服务器,并讲解如何在Apusic应用服务器部署这个组件。如果使用其他部署工具,原理是一样的。要使用Apusic应用服务器,可以到上下载试用版。
确定你的Apusic服务器已经被启动。 打开"部署工具"应用程序,点击文件->新键工程:
第一步:选择"新建包含一个 EJB组件打包后的EJB-jar模块"选项
第二步:选择一个刚才我们生成的bmp1Book.jar文件
第三步:输入一个工程名,可以随意,这里我们输入bmp1Book
第四步:输入工程存放的地址,这里我们假设被存放到D:\ejb\bmp1Book\deploy目录下
完成四个步骤后,如果没有问题将出现bmp1BookBean的部署界面,基本的参数配置已经在我们刚才编写的ejb-jar.xml中定义,虽然我们在部署时免去了映射字段、编写SQL操作语句的要求,但是需要提供一些BMP特性的配置:
选择bmp1Book的配置页,点击"4.资源引用",画面上应该出现了我们在ejb-jar.xml文件中设置的数据库引用,我们设置一下共项范围和JNDI名,设置后的画面如下图5-3:
<图5-3>
上述步骤完成后就可以点击部署->部署到Apusic应用服务器完成部署工作。
对于客户端,引用BMP实例的方式和引用CMP实例的方式是一样的,所以我们不需要改动上一节的servlet程序,只需做少许改动: Bmp1BookServlet .java文件:
…. public class Bmp1BookServlet extends HttpServlet{ …… } |
假设我们将文件保存到D:\ejb\Bmp1Book\src\Bmp1BookServlet.java
使用如下命令编译Servlet
|
编译成功后将这个servlet部署到与Bmp1Book同一工程中,在部署前需要我们编写一个web.xml,并制作成一个Web模块文件(war文件) web.xml文件内容如下:
|
假设我们将文件保存到D:\ejb\Bmp1Book\test\WEB-INF\web.xml
J2EE Web应用可以包括Java Servlet类、JavaServer Page组件、辅助的Java类、HTML文件、媒体文件等,这些文件被集中在一个War文件中。其中War结构具有固定的格式,根目录名为WEB-INF,同一目录下应该有一个web.xml文件,用来描述被部署文件的部署信息,Jsp、html等文件可以放置在这个目录下,同时WEB-INF目录下可能存在一个classes目录用于存放Servlet程序,如果引用了一些外部资源,则可以被放置到WEB-INF\lib目录下。使用下面的命令生成这个Servlet测试程序的war文件:
|
确保bmp1Book.war文件包括的文件目录格式如下:
WEB-INF<文件夹> classes<文件夹> bmp1BookServlet.class web.xml |
成功编译后,将这个servlet一同部署到bmp1Book工程中,我们回到"部署工具",点击编辑à填加一个Web模块,选择我们刚刚编译成的bmp1Book.war文件 点击部署->部署到Apusic应用服务器完成部署工作。
打开浏览器,在浏览器中输入:
localhost-Web Server的主机地址 :6888-应用服务器端口,根据不同的应用服务器,端口号可能不同 /bmp1Book-部署servlet时指定的WWW根路径值 /servlet-ejb容器执行servlet的路径 /Bmp1BookServlet-测试程序 |
如果运行正常,运行的结果应该和上一节CMP的例子相同
下载本文示例代码