实战ejb系列
在以后的日子里,将由jackliu向大家陆续提供一系列ejb教程,有学习ejb的朋友请同步参考ejb相关书籍,实战系列将以例程的方式帮助你理解这些基本的概念,其中将包括:
点击查看大图
所有章节完毕后将制作成pdf电子文档,供大家下载
实战ejb之五 开发实体bmp(ejb 1.1规范)
前一节介绍了entitybean的有关介绍,并通过开发、部署实体cmp的例子介绍ejb1.1规范的cmp的有关特性,在这一节中你将了解如下内容:
- ejb 1.1规范中的bmp
- 编写一个ejb 1.1 的bmp程序
- 部署到应用服务器
- 开发和部署测试程序
- 运行测试程序
ejb 1.1规范中的bmp
根据规范中定义的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 designtimesp=21804>
点击查看大图
<图5-2 designtimesp=21815>
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寿命周期
编写一个ejb 1.1 的bmp程序
上一节编写了一个cmp的例子,同样我们可以试者将它改写成一个bmp,假设功能需求不变化(功能介绍参看第四节的相关章节),为这个bmp起名为bmp1book 设计一个bmp bean与cmp同样至少包括四个步骤:
- 开发主接口
- 开发组件接口
- 开发bean实现类
- 编写部署文件
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:ejbbmp1booksrcbmp1bookhome.java
2.开发组件接口(bmp1book.java):
组件接口的设计与cmp的组件接口设计一样,参照上一节组件接口的设计,改动之处用黑体加粗显示。
bmp1book.java代码:
import javax.ejb.ejbobject; import java.rmi.remoteexception; //ejb bmp 1.1实战例子 public interface bmp1book extends ejbobject{ public void setbookname(string bookname) throws remoteexception; public void setbookprice(double bookprice) throws remoteexception; public string getbookname() throws remoteexception; public double getbookprice() throws remoteexception; }
|
假设我们保存到d:ejbbmp1book srcbmp1book .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代码:
import java.util.*; import javax.ejb.*; //引入sql处理包 import java.sql.*; import javax.sql.*; import javax.naming.*;
//ejb bmp 1.1实战例子 public class bmp1bookejb implements entitybean{ //保存bookid字段值 private string bookid; //保存bookname字段值 private string bookname; //保存bookprice字段值 private double bookprice;
private entitycontext ctx; private datasource ds; private string dbjndi="java:comp/env/jdbc/oadb"; private connection con; public void setbookname(string bookname){ this.bookname=bookname; } public void setbookprice(double bookprice){ this.bookprice=bookprice; } public string getbookname(){ return this.bookname; } public double getbookprice(){ return this.bookprice; } //bmp需要bean提供数据的插入 public string ejbcreate(string bookid,string bookname,double bookprice) throws createexception{ if(bookid==null) throw new createexception("the bookid is required"); try{ string sql="insert into book values(?,?,?)"; con=ds.getconnection(); preparedstatement stmt =con.preparestatement(sql); stmt.setstring(1,bookid); stmt.setstring(2,bookname); stmt.setdouble(3,bookprice); stmt.executeupdate(); stmt.close(); }catch (sqlexception se){ throw new ejbexception(se); }finally{ try{ if(con!=null) con.close(); }catch(sqlexception se){} } this.bookid=bookid; this.bookname=bookname; this.bookprice=bookprice; //由bean负责事务持久性,bean负责返回主键值 return bookid; } public void ejbpostcreate(string bookid,string bookname,double bookprice){} //根据bookid值提取数据 public void ejbload(){ try{ string sql="select bookid,bookname,bookprice from book where bookid=?"; con=ds.getconnection(); preparedstatement stmt =con.preparestatement(sql); stmt.setstring(1,this.bookid); resultset rset=stmt.executequery(); if(rset.next()){ this.bookname=rset.getstring("bookname"); this.bookprice=rset.getdouble("bookprice"); stmt.close(); }else{ stmt.close(); throw new nosuchentityexception("book id:"+this.bookid); } }catch (sqlexception se){ throw new ejbexception(se); }finally{ try{ if(con!=null) con.close(); }catch(sqlexception se){} } }
//保存被关联的数据记录 public void ejbstore(){ try{ string sql="update book set bookname=?,bookprice=? where bookid=?"; con=ds.getconnection(); preparedstatement stmt =con.preparestatement(sql); stmt.setstring(1,this.bookname); stmt.setdouble(2,this.bookprice); stmt.setstring(3,this.bookid); if(stmt.executeupdate()!=1){ stmt.close(); throw new ejbexception("occount a error on saved"); } stmt.close(); }catch (sqlexception se){ throw new ejbexception(se); }finally{ try{ if(con!=null) con.close(); }catch(sqlexception se){} } } //删除关联的记录 public void ejbremove(){ try{ string sql="delete from book where bookid=?"; con=ds.getconnection(); preparedstatement stmt =con.preparestatement(sql); stmt.setstring(1,this.bookid); if(stmt.executeupdate()!=1) throw new ejbexception("occount a error on remove"); stmt.close(); }catch (sqlexception se){ throw new ejbexception(se); }finally{ try{ if(con!=null) con.close(); }catch(sqlexception se){} } } public void unsetentitycontext(){ this.ctx=null; } //初始化数据库连接,初始化情境参数 public void setentitycontext(entitycontext context){ this.ctx=context; try{ initialcontext initial =new initialcontext(); ds=(datasource)initial.lookup(this.dbjndi); }catch(namingexception ne){ throw new ejbexception(ne); } } //在bean激活时,从情境参数中获取bean的主键值,然后会自动调用ejbload() public void ejbactivate(){ this.bookid=(string)ctx.getprimarykey(); } //解除当前bean实例与数据库记录的关系 public void ejbpassivate(){ this.bookid=null; } //根据主键查找对象 public string ejbfindbyprimarykey(string primarykey) throws finderexception{ try{ string sql="select bookid from book where bookid=?"; con=ds.getconnection(); preparedstatement stmt =con.preparestatement(sql); stmt.setstring(1,primarykey); resultset rset=stmt.executequery(); if(!rset.next()){ stmt.close(); throw new objectnotfoundexception(); } stmt.close(); //查到数据库中存在此条记录 return primarykey; }catch (sqlexception se){ throw new ejbexception(se); }finally{ try{ if(con!=null) con.close(); }catch(sqlexception se){} } } //查找书单定价在指定范围内的bean的集合 public collection ejbfindinprice(double lowerlimitprice,double upperlimitprice) throws finderexception{ try{ string sql="select bookid from book where bookprice between ? and ?"; system.out.println(sql); con=ds.getconnection(); preparedstatement stmt =con.preparestatement(sql); stmt.setdouble(1,lowerlimitprice); stmt.setdouble(2,upperlimitprice); resultset rset=stmt.executequery(); arraylist booklist=new arraylist(); while(rset.next()) booklist.add(rset.getstring("bookid")); stmt.close();
return booklist; }catch (sqlexception se){ throw new ejbexception(se); }finally{ try{ if(con!=null) con.close(); }catch(sqlexception se){} } } }
|
假设我们保存到d:ejbbmp1booksrcbmp1bookejb .java
到此为止我们的bean程序组件已经改写完毕了,使用如下命令进行编译:
cd beanbmp1book mkdir classes cd src javac -classpath %classpath%;../classes -d ../classes *.java
|
如果顺利你将可以在..bmp1bookclasses目录下发现有三个类文件。
4.编写部署文件:
ejb-jar.xml文件:
<?xml version="1.0" encoding="utf-8"?> <!doctype ejb-jar public "-//sun microsystems, inc.//dtd enterprise javabeans 2.0//en" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <description> this is bmp 1.1 book ejb example </description> <display-name>bmp1bookbean</display-name> <enterprise-beans> <entity> <display-name>bmp1book</display-name> <ejb-name>bmp1book</ejb-name> <home>bmp1bookhome</home> <remote>bmp1book</remote> <ejb-class>bmp1bookejb</ejb-class> <persistence-type><b>bean</b></persistence-type> <prim-key-class>java.lang.string</prim-key-class> <reentrant>false</reentrant> <b><resource-ref> <res-ref-name>jdbc/oadb</res-ref-name> <res-type>javax.sql.datasource</res-type> <res-auth>container</res-auth> </resource-ref> </b> </entity> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>bmp1book</ejb-name> <method-name>*</method-name> </method> <trans-attribute>notsupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
|
假设我们保存到d:ejbbmp1bookclassesmeta-infejb-jar.xml(注意meta-inf必须大写)
现在让我们看看当前的目录结构:
bmp1book <文件夹 designtimesp=22285> classes<文件夹 designtimesp=22287> meta-inf<文件夹 designtimesp=22289> ejb-jar.xml bmp1book .class bmp1book ejb.class bmp1bookhome.class src<文件夹 designtimesp=22296> bmp1book.java bmp1bookejb.java bmp1bookhome.java
|
部署到应用服务器
在部署之前我们需要将这些类文件和xml文件做成一个jar文件,ejb jar文件代表一个可被部署的jar库,在这个库里,包含了服务器代码与ejb模块的配置。ejb-jar.xml文件被放置在jar文件所指定的meta-inf目录中。我们可以使用如下命令得到ejb jar文件:
cd d:ejbbmp1bookclasses (要保证类文件在这个目录下,且有一个meta-inf子目录存放ejb-jar.xml文件) jar -cvf bmp1book.jar *.*
|
确保bmp1book.jar文件包括的文件目录格式如下:
meta-inf<文件夹 designtimesp=22329> ejb-jar.xml insufficientfundexception.class statefulaccount.class statefulaccountejb.class statefulaccounthome.class
|
部署工具一般由java应用服务器的制造商提供,在这里我使用了apusic应用服务器,并讲解如何在apusic应用服务器部署这个组件。如果使用其他部署工具,原理是一样的。要使用apusic应用服务器,可以到www.apusic.com上下载试用版。
确定你的apusic服务器已经被启动。 打开"部署工具"应用程序,点击文件->新键工程:
第一步:选择"新建包含一个 ejb组件打包后的ejb-jar模块"选项
第二步:选择一个刚才我们生成的bmp1book.jar文件
第三步:输入一个工程名,可以随意,这里我们输入bmp1book
第四步:输入工程存放的地址,这里我们假设被存放到d:ejbmp1bookdeploy目录下
完成四个步骤后,如果没有问题将出现bmp1bookbean的部署界面,基本的参数配置已经在我们刚才编写的ejb-jar.xml中定义,虽然我们在部署时免去了映射字段、编写sql操作语句的要求,但是需要提供一些bmp特性的配置:
选择bmp1book的配置页,点击"4.资源引用",画面上应该出现了我们在ejb-jar.xml文件中设置的数据库引用,我们设置一下共项范围和jndi名,设置后的画面如下图5-3:
<图5-3 designtimesp=22362>
上述步骤完成后就可以点击部署->部署到apusic应用服务器完成部署工作。
开发和部署测试程序
对于客户端,引用bmp实例的方式和引用cmp实例的方式是一样的,所以我们不需要改动上一节的servlet程序,只需做少许改动: bmp1bookservlet .java文件:
…. public class bmp1bookservlet extends httpservlet{ …… }
|
假设我们将文件保存到d:ejbbmp1booksrcbmp1bookservlet.java
使用如下命令编译servlet
cd d:ejbbmp1book mkdir test cd test mkdir web-inf cd web-inf mkdir classes cd d:ejbbmp1booksrc javac -classpath %classpath%;../classes/ -d ../test/web-inf/classes bmp1bookservlet.java
|
编译成功后将这个servlet部署到与bmp1book同一工程中,在部署前需要我们编写一个web.xml,并制作成一个web模块文件(war文件) web.xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<!doctype web-app public '-//sun microsystems, inc.//dtd web application 2.3//en' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app> <icon> <small-icon></small-icon> <large-icon></large-icon> </icon> <display-name>bmp1bookservlet</display-name> <description></description> <context-param> <param-name>jsp.nocompile</param-name> <param-value>false</param-value> </context-param> <context-param> <param-name>jsp.usepackages</param-name> <param-value>true</param-value> <description></description> </context-param> <ejb-ref> <description></description> <ejb-ref-name>ejb/bmp1book</ejb-ref-name> <ejb-ref-type>entity</ejb-ref-type> <home>bmp1bookhome</home> <remote>bmp1book</remote> <ejb-link>bmp1book</ejb-link> </ejb-ref> </web-app>
|
假设我们将文件保存到d:ejbbmp1book estweb-infweb.xml
j2ee web应用可以包括java servlet类、javaserver page组件、辅助的java类、html文件、媒体文件等,这些文件被集中在一个war文件中。其中war结构具有固定的格式,根目录名为web-inf,同一目录下应该有一个web.xml文件,用来描述被部署文件的部署信息,jsp、html等文件可以放置在这个目录下,同时web-inf目录下可能存在一个classes目录用于存放servlet程序,如果引用了一些外部资源,则可以被放置到web-inflib目录下。使用下面的命令生成这个servlet测试程序的war文件:
cd d:ejbbmp1book est jar -cvf bmp1book.war *.*
|
确保bmp1book.war文件包括的文件目录格式如下:
web-inf<文件夹 designtimesp=22483> classes<文件夹 designtimesp=22485> bmp1bookservlet.class web.xml
|
成功编译后,将这个servlet一同部署到bmp1book工程中,我们回到"部署工具",点击编辑à填加一个web模块,选择我们刚刚编译成的bmp1book.war文件 点击部署->部署到apusic应用服务器完成部署工作。
运行测试程序
打开浏览器,在浏览器中输入:
http://localhost:6888/bmp1book/servlet/bmp1bookservlet
localhost-web server的主机地址 :6888-应用服务器端口,根据不同的应用服务器,端口号可能不同 /bmp1book-部署servlet时指定的www根路径值 /servlet-ejb容器执行servlet的路径 /bmp1bookservlet-测试程序
|
如果运行正常,运行的结果应该和上一节cmp的例子相同
|