站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2EE 专题win2k环境下基于JBOSS的J2EE开发实践----之五:CMP实体Bean的编写与部署

win2k环境下基于JBOSS的J2EE开发实践----之五:CMP实体Bean的编写与部署

摘要: JBOSS,j2ee,Java,EJB,xml,CMP,BMP,部署,描述符

<table width=650 align=center border=1> <tr> <td>

win2k环境下基于JBOSS的J2EE开发实践

----之五:CMP实体Bean的编写与部署

</td></tr> <tr> <td>

一、 前言

在前面一系列文章的上一节中,我们给出了编写BMP的方法,并详细述说了BMP与CMP的区别和共性。
BMP和CMP同属EJB2.x规范中定义的实体Bean(Entity Bean),一个实体Bean是由多个类和接口组成,我们可以认为一个实体Bean代表数据库中的一个表(姑且这么认为),一个实体Bean的一个具体对象代表该表中的一条记录。也就是说,实体Bean是位于数据库和用户应用之间的数据中间件,关于为什么非要搞这个中间件,我在上一节中己经给出了充分的理由说明。
这个数据中间件由EJB容器负责管理,具体来说也即:当实体Bean部署时,EJB容器通过ejbLoad()方法将数据表中的数据填充到实体Bean并产生实体Bean对象;当实体Bean对象要释放时,EJB容器就调用实体Bean的ejbStore()方法将实体Bean对象中的数据存入数据表中;通过以上两步可以保持数据库表与实体Bean数据中间件同步。
首先,以上的同步方法在BMP中由我们自己来描述,具体见上一节,而在我们的CMP中,这些同步方法由CMP自动实现。
其次,CMP实体Bean不含有显示声明的字段,如我们在上一节BMP的Bean文件中显示声明的几个字段在CMP中是没有的。但是,它们如何和数据库表中对应的呢?事情是这样的,虽然没有显示声明,我们可以假想它们还是实际存在的(它声明在部署描述符文件中),我们通过一系列的getter/setter方法来设置它们的值。在BMP中,由我们自己来编写getter/setter方法,在CMP中,我们只需将这些getter/setter方法声明成abstract类型的,由EJB容器根据我们的布署描述符自动生成,于是,我们在我们的CMP Bean中就可以调用它了。
同时,BMP中的查询方法和CMP中的查询方法的实现方式也不同,在BMP中,我们在Home接口中定义的所有的查询实体Bean的查找方法都是我们自己来实现其细节的。然而在CMP中,这些查找方法我们也可以在编写Bean时省略,然后我们在布署描述文件中通过一种叫EJB-QL的语言(它类似于简单的SQL查询语言)来定义这些查找方法。
下面,我将详细说明具体编程上,二者有何不同及如何在JBOSS中编写和部署CMP。

二、 编写和布署CMP的方法步骤及其与BMP的区别

具体来说,编写部署BMP和CMP有以下相同和几个不同的地方:

1、 均需首先在数据库中定义该实体Bean对应的数据表

2、 均需配置数据库连接池支持文件,具体来说,请参见本系列教程之二------数据库连接池的配置与测试。这一步使得EJB容器可以通过连接池连接到数据库。

3、 编写Remote接口,这个CMP和BMP相同,均是编写一些暴露给远程调用的方法的声明,如商务逻辑方法。

4、 编写Home接口,这个CMP和BMP也相同,均是定义一些Home接口上的查询方法,注意,每一个实体Bean至少需要一个findByPrimaryKey(PK)方法。只不过,这里定义的查找方法在BMP中是由Bean类实现的,在CMP中,它们不由Bean类实现,是由部署描述符文件用EJB-QL声明并由EJB容器实现的。

5、 编写主键类,这个二者相同。

6、 编写Bean类,这是BMP和CMP最大的区别,在BMP中,我们需要明确的实现getter/setter方法,需要明确地实现ejbLoad()/ejbStore()方法和各种查找方法,及连接数据库的方法;而在我们的CMP中,这些方法我们只需要简单的声明一下,实现一个空的方法体就可以了,查找方法不用声明即可。同时,我们需要把getter/setter方法声明为abstract类型的,它由EJB容器实现,我们可以在Bean方法中调用它。(具体见后面示例的程序)

7、 编写ejb-jar.xml文件,这是EJB2.0规范标准定义的,由它定义我们的实体Bean的各种信息。这一点BMP和CMP是相同的,但是,在CMP的ejb-jar.xml文件中,除了定义Bean的各种属性如name,Remote,Home接口等信息外,还需定义实体Bean中getter/setter对应方法对应的声明字段信息,EJB容器根据这些声明的字段来维护实体Bean和数据库表字段之间的对应关系;同时,在CMP的ejb.jar.xml文件中,我们还需用EJB-QL语言描述我们在Home接口中定义的查找方法。也就是说,CMP的描述符文件ejb-jar.xml比BMP的ejb-jar.xmll多了两部分:一是声明由EJB容器维护的字段;二是用EJB-QL方法声明Home接口中定义的查找方法。这里说明一下,EJB-QL方法和SQL语法类似,它包括基本的Select ,From,Where等子句,具体可以查看EJB-QL参考。

8、 编写jboss-service.xml文件,这个文件声明实体Bean的Jndi信息,通过它的内容,我们可以在调用EJB的程序中通过lookup(“jndi-name”)查找到该Bean并调用它。

9、 编写jbosscmp-jdbc.xml,这是一个CMP特有的文件,它的内容主要是声明两个大方面的内容:一、声明我们的CMP实体Bean和数据库表之间的对应的关系,即声明我们的CMP通过哪个数据库连接池连接到数据库并和哪个数据库表对应。二、声明我们的CMP中的字段和数据库表中的哪个字段对应,这些字段数据类型之间的对应关系是什么。
这些步骤是我们在JBOSS中编写和布署CMP实体Bean的必需的方法,在其它开发平台和不同的容器下,也基本相同,我们下面将通过自己编写所有的文件来实现部署CMP,以使读者掌握各个文件和字段的作用,了解了这些后,相信在其它自动化的环境中编写和部署EJB这是很容易的事。

三、 范例CMP实体Bean的编写与部署文件的编写

这里,我们仍采用Mastering EJB一书中的示例来给大家讲解。这里对该书中的例子做了少许改动。
1、 第一步,我们定义该实体Bean对应的数据库表。我们这里采用mySQL数据库做为后库数据库支持,其它的数据库也是一样的,我们在mySQL安装后的一个叫test的数据库中,用如下的SQL语句来创建我们的数据库表:


USE test;
DROP TABLE IF EXISTS `productbean`;
CREATE TABLE `productbean` (
`productID` varchar(30) ,
`name` varchar(60) ,
`description` varchar(120) ,
`basePrice` double
) ;


此数据表对应于我们将要编写的CMP实体Bean即ProductBean。

2、 设置数据库连接池,详细说明请见本系列教程之二,在这里,我们就是在JBOSS的服务器发布目录C:\JBOSS\server\all\deploy下新建一个mysql-ds.xml文件,其内容如下:


<?xml version="1.0" encoding="gb2312"?>


MySql
jdbc:mysql://10.0.0.18:3306/test
org.gjt.mm.mysql.Driver
root



以上内容在JBOSS配置了一个mysql数据库连接池,mySQL数据库在10.0.0.18这台服务器上,连接端口为默认的3306端口,并定义连接到的数据库为Test这个库,同时我们指定在EJB中可以通过jndi名为MySql来访问并获得连接。

3、 好了,前期工作己经做好,下面我们来编写我们CMP相关的类和接口,首先我们还是要建立我们的开发目录,我们这里的环境是这样的(具体请见前面几节说明):我们的JBOSS装在C:\JBOSS目录,我们在它下面建了一个myproject目录用于放置我们的所有的开发项目。这里,我们在myproject下面建一个ProductCMP目录用于放我们这次开发的CMP。然后同前面一样,我们在ProductCMP目录下建一系列的目录结构分别用于放我们的源程序及发布部署文件。建好后的目录结构如下图1所示:

图1

其中,我们的所有的源程序均放在src目录中,product.jar目录放我们的部署到JBOSS容器中的CMP类,client目录放置我们的客户端测试程序相关的类。其中,我们的所有EJB类均放入product.ejb包中。所以建了两层目录。

4、 好了,目录建好了,我们下面开始编写我们的CMP各种类和接口,首先编写Remote接口,它放入src目录中,代码如下:


//Product.java
package product.ejb;
import javax.ejb.*;
import java.rmi.RemoteException;
public interface Product extends EJBObject{
// 开始 get/set 方法,这里的 getter/setter 方法名称由 get/set 开头,后面紧跟着 CMP
// 需管理域的名字,并且把此名字第一个字母大写,这些 CMP 管理的域在 ejb-jar.xml 中声明
// 这些方法此处要暴露给远程调用
public String getName() throws RemoteException;
public void setName(String name) throws RemoteException;
public String getDescription() throws RemoteException;
public void setDescription(String description) throws RemoteException;
public double getBasePrice() throws RemoteException;
public void setBasePrice(double price) throws RemoteException;
public String getProductID() throws RemoteException;
public void setProductID(String productID) throws RemoteException;
}


5、 接着在src目录编写Home接口,内容如下:


//ProductHome.java
package product.ejb;
import javax.ejb.*;
import java.rmi.RemoteException;
import java.util.Collection;
public interface ProductHome extends EJBHome{
Product create(String productID,String name,String description,double basePrice) throws
CreateException,RemoteException;
public Product findByPrimaryKey(ProductPK key) throws FinderException,RemoteException;
public Collection findByName(String name) throws FinderException,RemoteException;
public Collection findByDescription(String description) throws FinderException,RemoteException;
public Collection findByBasePrice(double basePrice) throws FinderException,RemoteException;
public Collection findExpensiveProducts(double minPrice) throws FinderException,RemoteException;
public Collection findCheapProducts(double maxPrice) throws FinderException,RemoteException;
public Collection findAllProducts() throws FinderException,RemoteException;
}

在上面的Home接口中,我们声明了一个Create方法,它和我们下面要写的Bean类中的ejbCreate方法对应,只不过这里它返回的是一个Product对象。在Home接口中我们还定义了几个查找方法。查找方法返回的大多都是Collection集合,因为,查询的结果可以是多个。这些查找方法我们将在ejb-jar.xml中用EJB-QL描述。

6、 编写主键类,这个和BMP相同。这的内容如下:


//ProductPK.java
package product.ejb;
import java.io.Serializable;
public class ProductPK implements java.io.Serializable{
public String productID;
public ProductPK(String productID){
this.productID = productID;
}
public ProductPK(){
}
public String toString(){
return productID.toString();
}
public int hashCode(){
return productID.hashCode();
}
public boolean equals(Object prod){
return ((ProductPK)prod).productID.equals(productID);
}
}

7、 接着就要编写我们的Bean类,这个类由于在它其中需要声明abstract型的getter/setter方法,所以它本身也需要声明为Abstract类型的。同时,它没有了像BMP那样的实现由EJB容器调用的方法。它的代码如下:


package product.ejb;
import javax.ejb.*;
//此处注意要声明为抽象类型的
public abstract class ProductBean implements EntityBean{
private EntityContext ctx;
public ProductBean(){
}
// 开始抽象的 get/set 方法,这里的 getter/setter 方法名称由 get/set 开头,后面紧跟着 CMP
// 需管理域的名字,并且把此名字第一个字母大写,这些 CMP 管理的域在 ejb-jar.xml 中声明
public abstract String getName();
public abstract void setName(String name);
public abstract String getDescription();
public abstract void setDescription(String description);
public abstract double getBasePrice();
public abstract void setBasePrice(double price);
public abstract String getProductID();
public abstract void setProductID(String productID);
//开始EJB必须的方法,方法由容器调用,不是由客户端程序调用,这里只需声明即可
public void ejbActivate(){}
public void ejbRemove(){}
public void ejbPassivate(){}
public void ejbLoad(){}
public void ejbStore(){}
public void setEntityContext(EntityContext ctx){
this.ctx = ctx;
}
public void unsetEntityContext(){
this.ctx = ctx;
}
public void ejbPostCreate(String productID,String name,String description,double basePrice){}
//这个Create和Home接口中的Create对应,这里它要返回一个主键类
//它调用由容器实现的getter/setter方法来产生实体Bean对象。
public ProductPK ejbCreate(String productID,String name,String description,double basePrice) throws CreateException{
setProductID(productID);
setName(name);
setDescription(description);
setBasePrice(basePrice);
return new ProductPK(productID);
}
}

8、 好了,通过以上几步,我们编写好了CMP所需的类文件,下面我们来编写部署此CMP的部署描述符文件。首先编写的是ejb-jar.xml,进入META-INF目录(即C:\JBOSS\myproject\ProductCMP\ejb\product.jar\META-INF),在此目录中新建一个ejb-jar.xml文件,内容如下:(这个文件的说明我写在文件中了,是一个自解说的文件)


<?xml version="1.0" encoding="gb2312"?>
ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/dtd/ejb-jar_2_0.dtd">



Product product.ejb.ProductHome
product.ejb.Product
product.ejb.ProductBean
Container product.ejb.ProductPK
False
2.x


9、 下面来编写jboss-service.xml文件,这个文件是JBOSS服务器特有的文件,它指明了这个EJB的jndi名称,方便调用EJB。这个文件也放入META-INF目录中:内容如下:


<?xml version="1.0" encoding="gb2312"?>

  
    
       Product
       Product

10、 最后,编写jbosscmp-jdbc.xml,这是一个JBOSS中部署CMP实体Bean特有的描述符文件,这个文件描述了实体Bean和数据库表的对应关系,同时它也描述了实体Bean维护的声明字段和数据库表中字段的对应关系及数据类型的对应关系。它也放入META-INF目录中,内容如下,这个文件的解说我放入在文件之中了。


<?xml version="1.0" encoding="gb2312"?>
jbosscmp-jdbc PUBLIC "-//JBoss//DTD JBOSSCMP-JDBC 3.0//EN" "http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_3_0.dtd">


到此,我们己经编写了CMP所需的全部java类和部署描述符文件,共有三个描述符文件,可以说BMP中在Bean类中实现的部分现在大部分都转移到CMP的部署描述符文件中了。这三个描述符文件分别叫ejb-jar.xml、jboss-service.xml、和jbosscmp-jdbc.xml文件,名称必须对应。且它们全部都放在MEAT-INF目录下。

四、 部署和测试CMP

编写好了CMP所需的文件后,下面我们来在JBOSS中编译、部署并测试这个CMP实体Bean。
首先、我们来编写测试此CMP的客户端程序,进入SRC目录,新建一个ProductClient.java,内容如下:


//ProductClient.java客户端测试程序
package product.ejb;
import javax.ejb.*;
import javax.naming.*;
import java.rmi.*;
import javax.rmi.PortableRemoteObject;
import java.util.*;
import java.io.*;
//CMP客户端
public class ProductClient{
public static void main(String[] args) throws Exception{
ProductHome productHome = null;
try{
Properties env = new Properties();
//config.properties文件应该放在和hello包目录所在目录的同级目录中。即它和hello文件夹同在一个文件夹中。
env.load(new FileInputStream("config.properties"));
// Get a naming context
System.out.println(env);
Context ctx = new javax.naming.InitialContext(env);
System.out.println("Got context");
//Search from jndi tree to get Home Object
productHome = (ProductHome)PortableRemoteObject.narrow(ctx.lookup("Product"),ProductHome.class);
//创建一些对象
productHome.create("123-456-7890","p5-350","350Mhz Pentium",200);
productHome.create("123-456-7891","p5-400","400Mhz Pentium",300);
Product thisProduct = productHome.create("123-456-7892","p5-450","450Mhz Pentium",400);
productHome.create("123-456-7893","SD-64","64MB SDRAM",50);
productHome.create("123-456-7894","SD-128","128MB SDRAM",100);
productHome.create("123-456-7895","SD-256","256MB SDRAM",200);

//输出产品的信息,引用创建时产生的thisProduct对象
System.out.println("thisProduct's info:"+thisProduct.getProductID()+thisProduct.getName()+thisProduct.getDescription()+thisProduct.getBasePrice());
//查找一个产品
Iterator i = productHome.findByName("SD-64").iterator();
System.out.println("These products match the same SD-64:");
while(i.hasNext()){
Product product = (Product)PortableRemoteObject.narrow(i.next(),Product.class);
System.out.println(product.getDescription());
}//end while
//查找所有价值为200元的产品
System.out.println("Finding all products that cost$200:");
i = productHome.findByBasePrice(200).iterator();
while(i.hasNext()){
Product product = (Product)PortableRemoteObject.narrow(i.next(),Product.class);
System.out.println(product.getDescription());
}//end while
}catch(Exception e){
e.printStackTrace();
}finally{
if(productHome!=null){
System.out.println("Delete all price >=300 products");
Iterator i = productHome.findExpensiveProducts(300).iterator();
while(i.hasNext()){
try{
Product product = (Product)PortableRemoteObject.narrow(i.next(),Product.class);
product.remove();
}catch(Exception e){
e.printStackTrace();
}
}//end while
}//end if
}//end finally
}//end main
}//end class

然后,进入src目录,执行:

com *.java

com是com.bat,是我们在本系列文章中之一中编写的一个编译EJB类的批处理文件,请参见第一节。执行后,产生五个class文件。
分别把Product.classs、ProductHome.class、ProductBean.class和ProductPK.class拷入:
C:\JBOSS\myproject\ProductCMP\ejb\product.jar\product\ejb目录中,

把Product.class,ProductHome.class、ProductPK.class和ProductClient.class文件拷入:
C:\JBOSS\myproject\ProductCMP\ejb\client\product\ejb目录中,

然后,再在C:\JBOSS\myproject\ProductCMP\ejb\client目录中新建一个config.properties文件,内容如下:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming.client
java.naming.provider.url=jnp://10.0.0.18:1099
这个文件的解说见前面几节中的说明。

布署CMP,把product.jar目录整个拷入到C:\JBOSS\server\all\deploy目录下,启动JBOSS服务器,查看启动过程中有没有异常抛出。若无异常,则证明部署成功。下面对此CMP进行测试。进入C:\JBOSS\myproject\ProductCMP\ejb\client目录,执行:

runclient product/ejb/ProductClient

出现如下图2所示画面,证明运行成功!

图2

五、 总结

这一节中,我们给大家讲解了如何部署和编写CMP实体Bean,在实际的使用过程中,CMP比BMP使用的要多一些。CMP的关键之处在于如何处理和编写部署描述符文件。
在发出本系列初学者教程之后,许多网友写信给我,有许多网友反应只能看到本系列教程的其中一篇,其实本教程王篇所有的都可以通过以下地址得到:

http://www.csdn.net/Develop/list_article.asp?author= abnerchai

同时,有许多网友来信说,按照文中所述做了,但有错误,我在这里提醒大家几点:
1、 源程序从网页上拷贝下来后,一定要把程序和xml文件中的全角字符去掉,一定要验证xml文件的格式正确。该注解的地方是注解,不要把注解当成源程序了。
2、 一定要参见本系列教程的第一篇,那里,我们详细说明了如何安装和设置系统环境及ClassPath,我们采用的两个批处理来设置classPath,你一定要遵守。
3、 对于许多网友说有NULL 错误,建议大家发现这种错误时,要学会从打印出的异常信息判断错误出在哪里,你可以每运行一步打印出一个提示信息,看看到哪一步出现Null,就可以得出为什么并加以解决了。
4、 要深入理解这些部署文件xml文件中每一个元素的作用并加以有效的利用。

最后,由于这一段时间本人比较忙,手头上有个项目的活很多,对于部分网友的来信我没有一一答复,请大家原谅!

</td></tr> <tr> <td> </td></tr></table>



↑返回目录
前一篇: 初碰J2EE:RMI灰头灰脑篇
后一篇: J2EE应用程序部署的一些建议(3)