专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)

来源:互联网 发布:买家淘宝客怎么推广 编辑:程序博客网 时间:2024/05/16 04:43
<script type="text/javascript">google_ad_client = "pub-8800625213955058";/* 336x280, 创建于 07-11-21 */google_ad_slot = "0989131976";google_ad_width = 336;google_ad_height = 280;//</script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>

实战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>

<图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寿命周期

编写一个EJB 1.1 的BMP程序

上一节编写了一个CMP的例子,同样我们可以试者将它改写成一个BMP,假设功能需求不变化(功能介绍参看第四节的相关章节),为这个BMP起名为Bmp1Book 设计一个BMP Bean与CMP同样至少包括四个步骤:

  1. 开发主接口
  2. 开发组件接口
  3. 开发Bean实现类
  4. 编写部署文件

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代码:


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:/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代码:


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:/ejb/Bmp1Book/src/Bmp1BookEJB .java

到此为止我们的Bean程序组件已经改写完毕了,使用如下命令进行编译:


cd bean/Bmp1Bookmkdir classescd srcjavac -classpath %CLASSPATH%;../classes -d ../classes  *.java

如果顺利你将可以在../Bmp1Book/classes目录下发现有三个类文件。

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:/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应用服务器,可以到www.apusic.com上下载试用版。

确定你的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


cd D:/ejb/Bmp1Bookmkdir testcd testmkdir WEB-INFcd WEB-INFmkdir classescd D:/ejb/Bmp1Book/srcjavac -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:/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文件:


cd D:/ejb/Bmp1Book/test/jar -cvf bmp1Book.war *.*

确保bmp1Book.war文件包括的文件目录格式如下:


WEB-INF<文件夹> classes<文件夹>   bmp1BookServlet.class web.xml

成功编译后,将这个servlet一同部署到bmp1Book工程中,我们回到"部署工具",点击编辑à填加一个Web模块,选择我们刚刚编译成的bmp1Book.war文件 点击部署->部署到Apusic应用服务器完成部署工作。

运行测试程序

打开浏览器,在浏览器中输入:


http://localhost:6888/bmp1Book/servlet/Bmp1BookServletlocalhost-Web Server的主机地址:6888-应用服务器端口,根据不同的应用服务器,端口号可能不同/bmp1Book-部署servlet时指定的WWW根路径值/servlet-ejb容器执行servlet的路径/Bmp1BookServlet-测试程序

如果运行正常,运行的结果应该和上一节CMP的例子相同

原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 我听评书网 恋听网听书 妖夏闲听落花 睡前听故事 听评书网 听妈妈 骇人听闻 100首必听经典老歌 听心原唱 性读听吧 听什么有助于睡眠 听天下 听不见花落的声音 耳聋听不见 在清华听演讲 我听不见我看不见 听渔轩渔具店 听平书杨家将 一路听天下 听心经的好处 随车听 听西瓜 听潮阁 越听越聪明 婴儿听的歌曲 听是谁在唱歌 岳飞传在线听 听蜀僧睿弹琴 听蜀僧浚弹琴 怎么唱歌更好听 听城二手房 在线听 声呐 录歌 100首必听经典老歌目录 听书软件 有声听书吧 听书宝 听书app 酷我听书 听书大全