Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1530147
  • 博文数量: 3500
  • 博客积分: 6000
  • 博客等级: 准将
  • 技术积分: 43870
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-03 20:31
文章分类

全部博文(3500)

文章存档

2008年(3500)

我的朋友

分类:

2008-05-04 19:53:36

一起学习

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

mkdir classes

cd src

javac -classpath %CLASSPATH%;../classes -d ../classes  *.java

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

4.编写部署文件:

ejb-jar.xml文件:










        

        This is BMP 1.1 Book EJB example

    

    Bmp1BookBean

    

        

            Bmp1Book

            Bmp1Book

            Bmp1BookHome

            Bmp1Book

            Bmp1BookEJB

            Bean

            java.lang.String

            False

            

               jdbc/oadb

               javax.sql.DataSource

               Container

                                    

        

    

    

      

        

          Bmp1Book

          *

        

        NotSupported

      

    



假设我们保存到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




cd D:\ejb\Bmp1Book

mkdir test

cd test

mkdir WEB-INF

cd WEB-INF

mkdir classes

cd D:\ejb\Bmp1Book\src

javac -classpath %CLASSPATH%;../classes/ -d ../test/WEB-INF/classes Bmp1BookServlet.java

编译成功后将这个servlet部署到与Bmp1Book同一工程中,在部署前需要我们编写一个web.xml,并制作成一个Web模块文件(war文件) web.xml文件内容如下:














  

    

    

  

  Bmp1BookServlet

  

  

    jsp.nocompile

    false

  

  

    jsp.usePackages

    true

    

  

  

    

    ejb/Bmp1Book

    Entity

    Bmp1BookHome

    Bmp1Book

    Bmp1Book

  



假设我们将文件保存到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应用服务器完成部署工作。

运行测试程序

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






localhost-Web Server的主机地址

:6888-应用服务器端口,根据不同的应用服务器,端口号可能不同

/bmp1Book-部署servlet时指定的WWW根路径值

/servlet-ejb容器执行servlet的路径

/Bmp1BookServlet-测试程序

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

下载本文示例代码


专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)专稿:实战EJB之五 开发实体BMP(EJB 1.1规范)
阅读(262) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~