本章前面介绍的实体Bean开发方法都是基于CMP的实体Bean模型,它是应用非常广泛的实体Bean,借助容器实现的连接管理等支持条件,使CMP的性能很稳定。但是在有些情况下需要使用BMP作为补充。本节的主要内容将介绍基于JBoss开发BMP类型实体Bean的方法。
3.6.1 BMP简介 BMP和CMP都是管理数据永久性的实体Bean对象,它们都需要建立与数据库的连接,管理数据的建立、查询和删除等操作。不同之处在于,CMP与数据库之间的交互工作是由J2EE服务器负责的,CMP的开发者无需理会实体Bean与数据库的连接问题;对于BMP则不同,数据库的管理方法要由BMP的开发者编码完成,服务器维护着BMP对象,在客户端调用时执行BMP中的操作代码。所以,对于开发者来说,BMP的开发要较CMP略复杂一些。下面将通过开发实例来介绍BMP的开发方法。
3.6.2 BMP程序实例 本节将介绍一个管理程序员信息的示例,用BMP来实现,取名为Programmer BMPBean。
3.6.2.1 主接口 package com.liuyang.bmp.programmer;
import javax.ejb.*;
import java.rmi.RemoteException;
import java.util.Collection;
public interface ProgrammerBMPHome extends javax.ejb.EJBHome{
public static final String COMP_NAME="java:comp/env/ejb/ProgrammerBMP";
public static final String JNDI_NAME="ejb/ProgrammerBMP";
public ProgrammerBMP create(String name,int age,String language,String tool) throws CreateException,RemoteException;
public ProgrammerBMP findByPrimaryKey(String name) throws FinderException,RemoteException;
public Collection findByLanguage(String language) throws FinderException,RemoteException;
public Collection findByTool(String language) throws FinderException,RemoteException;
public Collection findOldThan(int age) throws FinderException,RemoteException;
}
BMP的主接口和CMP的主接口没有什么区别,但是在实现主接口中的查找方法时却有不同,CMP的查找方法可以由容器实现,但是在BMP中必须由BMP实现类实现,而且在命名时要在BMP实现类的方法名称前加上ejb前缀。
3.6.2.2 远程接口 package com.liuyang.bmp.programmer;
import java.rmi.RemoteException;
public interface ProgrammerBMP extends javax.ejb.EJBObject{
public int getAge( )throws RemoteException;
public String getLanguage( )throws RemoteException;
public String getName( )throws RemoteException;
public String getTool( )throws RemoteException;
public void setAge(int age)throws RemoteException;
public void setLanguage( String language )throws RemoteException;
public void setName( String name )throws RemoteException;
public void setTool( String tool )throws RemoteException;
}
BMP的远程接口与CMP一致。在这里为Programmer对象设置了4个字段:name, Age, Language和Tool。
3.6.2.3 实现类 BMP的实现类需要完成很多对于CMP来说不需要实现的方法,所以BMP的实现类比较复杂。BMP需要实现下面所列的一系列方法。
1.ejbCreate
public String ejbCreate(String key, int age,String language,String tool) throws CreateException {
Connection con = null;
PreparedStatement ps = null;
try {
con = datasource.getConnection();
ps = con.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES (?,?,?,?)");
ps.setString(1, key);
ps.setInt(2, age);
ps.setString(3, language);
ps.setString(4, tool);
ps.execute();
}catch (Exception e) {
throw new EJBException (e);
}finally {
try {
if(ps != null){ps.close();}
if(con != null){con.close();}
}catch (Exception ignore) {
}
}
return key;
}
ejbCreate方法中创建数据记录的过程在CMP中是不需要开发者来实现的,但是在这里,BMP需要利用Java的数据库API实现。TABLE_NAME是对应数据库表的名称,这里使用的是MySQL数据库中的Programmer表,在后面的部署文件生成时会指定资源为MySQL数据库。Programmer表需要手工创建。
2.ejbFindByPrimaryKey
public String ejbFindByPrimaryKey(String key)throws FinderException{
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
boolean found = false;
try {
con = datasource.getConnection();
ps = con.prepareStatement("SELECT name FROM " + TABLE_NAME + " WHERE name=?");
ps.setString(1, key);
rs = ps.executeQuery();
found = rs.next();
}catch (Exception e) {
throw new EJBException(e);
}finally {
try {
if (rs != null) {rs.close ();}
if (ps != null) {ps.close ();}
if (con != null) {con.close ();}
}catch (Exception ignore) {
}
}if (!found) {
throw new ObjectNotFoundException("No bean with name=" + key + " found.");
}
return key;
}
这个方法是实现主键查找的方法,在CMP中不需要实现,在这里也需要使用Java数据库API来实现。
3.ejbLoad
public void ejbLoad() throws EJBException, RemoteException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = datasource.getConnection();
ps = con.prepareStatement("SELECT name,age,language,tool FROM " + TABLE_NAME + " WHERE name=?");
ps.setString(1, (String)ctx.getPrimaryKey());
rs = ps.executeQuery();
if (rs.next()) {
this.name = rs.getString("name");
this.age = rs.getInt("age");
this.language = rs.getString("language");
this.tool = rs.getString("tool");
}
} catch (Exception e) {
e.printStackTrace();
throw new EJBException(e);
}finally {
try {
if (rs != null)rs.close();
if (ps != null)ps.close();
if(con != null)con.close();
} catch (Exception ignore) {
}
}
}
ejbLoad方法中的代码实现了从数据库中检索数据的工作,并将得到的数据存入BMP对象。
4.ejbRemove
public void ejbRemove()throws RemoveException, EJBException, RemoteException {
Connection con = null;
PreparedStatement ps = null;
try {
con = datasource.getConnection();
ps = con.prepareStatement("DELETE FROM " + TABLE_NAME + " WHERE name=?");
ps.setString(1, name);
ps.execute();
}catch (Exception e) {
throw new EJBException(e);
}finally {
try {
if (ps != null)ps.close();
if (con != null)con.close();
}catch (Exception ignore) {
}
}
}
ejbRemove方法的作用是将数据从数据库表中删除,在方法的代码中执行了对数据进行删除的SQL语句。
5.ejbStore
public void ejbStore() throws EJBException, RemoteException {
Connection con = null;
PreparedStatement ps = null;
try {
con = datasource.getConnection();
ps = con.prepareStatement("UPDATE " + TABLE_NAME + " SET age=?,language=?,tool=? WHERE name=?");
ps.setInt(1, this.age);
ps.setString(2, this.language);
ps.setString(3, this.tool);
ps.setString(4, this.name);
ps.execute();
}catch (Exception e) {
throw new EJBException(e);
}finally {
try {
if (ps != null)ps.close();
if (con != null)con.close();
}catch (Exception ignore) {
}
}
}
ejbStore方法负责将BMP中缓存的数据存入数据库中。
6.EntityContext
public void setEntityContext(EntityContext context)
throws EJBException, RemoteException {
try {
this.ctx = context;
datasource = (DataSource) new
InitialContext().lookup(DATASOURCE_NAME);
}catch (NamingException ne) {
throw new EJBException(ne);
}
}
public void unsetEntityContext()throws EJBException,RemoteException{
ctx = null;
}
编写CMP时开发者不一定需要用到EntityContext,但在BMP中EntityContext很重要,通过EntityContext可以传递主键。
除了上述基本的方法外,对于主接口中的每个查询方法,BMP都需要一一实现,以ejbFindOldThan方法为例:
public Collection ejbFindOldThan(int _age) throws FinderException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
Vector result = new Vector();
try {
con = datasource.getConnection();
ps = con.prepareStatement("SELECT name FROM " + TABLE_NAME + " where age>?");
ps.setInt(1, _age);
rs = ps.executeQuery();
while (rs.next()) {
result.add(rs.getString("name"));
}
}catch (Exception e) {
throw new EJBException(e);
}finally {
try {
if (rs != null)rs.close();
if (ps != null)ps.close();
if (con != null)con.close();
}catch (Exception ignore) {
}
}
return result;
}
在BMP中,开发者需要编写特定的数据库查询程序去实现主接口中各个查询方法,这使得开发者的任务加大,但是也使开发者开发实体Bean的灵活性增强了。为了减轻开发的负担,本书建议使用XDoclet工具或者在JBossIDE的环境下开发。接下来将介绍如何利用JBossIDE完成BMP的开发。
3.6.3 利用JBossIDE开发BMP 开发BMP的实现方法,确实是比较烦琐的任务,但是为了实现特定的功能而编写的数据库程序代码,没办法用其他工具生成,只好在其后的过程中使用JBossIDE工具减轻一些开发负担。借助上一节实现的ProgrammerBMPBean,本节将直接利用JBossIDE生成接口和配置文件,并打包部署到服务器。
在ProgrammerBMPBean前面的部分加入下列标记:
/**
* @author liuyang
*
* @ejb.bean description="ProgrammerBMPBean" :在ejb-jar.xml中添加描述信息
* display-name="ProgrammerBMPBean" :添加显示名称
* jndi-name="ejb/ProgrammerBMP" :设定JNDI名称
* name="ProgrammerBMP" :设定EJB名称
* type="BMP" * :设定实体Bean类型为BMP
* view-type="remote" :设定接口为远程接口
* primkey-field = "name" :设定数据表主键为name
*
* @ejb.resource-ref :在ejb-jar.xml中声明资源引用
* res-ref-name = "datasource" :引用的资源名称
* res-type = "javax.sql.DataSource"
* res-auth = "Container" :资源认证类型
*
* @jboss.resource-ref :在jboss.xml中声明资源引用
* jndi-name = "java:/MySQLDS" :设定资源的JNDI名称
* res-ref-name = "datasource" :设定资源的引用名称
*/
ProgrammerBMPBean在类层上的标记中,以@ejb开始的标记将被反映到ejb-jar.xml文件之中,以@jboss开始的标记将被反映到jboss.xml文件之中,BMP实体Bean的部署不需要使用jbosscmp-jdbc.xml。在这里,将BMP的数据库资源指向了JNDI名称为java:/MySQLDS的MySQL数据库,这个BMP实体Bean操作的数据表在程序中已经被指定为Programmer,JBoss不会为这个BMP实体Bean自动生成数据库表,所以在这个BMP实体Bean部署之前,需要通过手工操作的方式在MySQL数据库中创建Programmer数据表。
完成类层次的标记任务之后,需要为ProgrammerBMPBean中的一些方法进行标记。在get方法前加入下列标记:
/**
* @ejb.interface-method
* view-type = "remote" : 将这个方法暴露在远程接口之中
*
*/
在set方法前加入下列标记:
/**
* @ejb.interface-method
* view-type = "remote" : 将这个方法暴露在远程接口之中
*/
在ejbCreate方法前加入下列标记:
/**
* @ejb.create-method : 指定这个方法是EJB的创建方法
*/
public String ejbCreate(...
完成XDoclet标记后,下面就需要为BMP配置XDoclet任务。
(1)创建一个BMP配置,如图3-18所示。
(2)为BMP配置添加ejbdoclet任务,如图3-19所示。
设定ejbdoclet的destDir属性为build,ejbSpec属性为2.0。ejbdoclet提供了处理EJB对象的任务类型,此处的程序类型是BMP,所以选用ejbdoclet。
(3)为ejbdoclet添加fileset项。如图3-20所示。
设定fileset的dir属性为src,includes属性为com/liuyang/bmp/programmer/*.java。
fileset项在ejbdoclet内部指定了XDoclet处理的源代码对象,此处指向src目录,包含includes属性设定范围内的Java文件。如果fileset的dir属性指向的目录中,有些文件是不需要的,那么就需要用到fileset项的excludes属性。在excludes属性中设置的文件对象会被从dir目录下扣除。事实上,这里的fileset项对应的就是Ant build中的fileset,只是被应用到了ejbdoclet这个特殊的任务当中而已。
图3-18 创建一个BMP配置
图3-19 添加ejbdoclet任务
图3-20 添加fileset项
(4)为ejbdoclet添加deploymentdescriptor项。如图3-21所示。设定deployment- descriptor的destDir属性为build/META-INF。deploymentdescriptor项可以产生EJB的部署文件,destDir属性设定了生成的部署文件的位置。
图3-21 添加deploymentdescriptor项
(5)为ejbdoclet添加homeinterface项,如图3-22所示。
图3-22 添加homeinterface项
设定homeinterface的destDir属性为src。homeinterface项可以产生主接口代码,destDir属性设置了产生代码放置的位置。
(6)为ejbdoclet添加remoteinterface项。用同样的方法为ejbdoclet添加remoteinterface项,并设置destDir属性为src。
(7)为ejbdoclet添加jboss项,如图3-23所示。
图3-23 添加jboss项
设定jboss的destDir属性为build/META-INF,Version属性为3.2。
jboss项可以为BMP产生jboss.xml配置文件,destDir属性设置了产生jboss.xml放置的位置,JBoss版本被设置为3.2版。
最后,在Eclipse中运行XDoclet,将产生BMP的两个接口和两个部署文件。打包BMP的方法与CMP基本相同,只是比CMP少了一个jbosscmp-jdbc.xml文件,所以本节将该步骤略去。
由于JBoss服务器不会为BMP自动产生数据库表,所以需要手工操作。
本书将MySQL中的jbossdb数据库配置到JBoss中作为资源,配置信息如下:
<datasources>
<local-tx-datasource>
<jndi-name>MySQLDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/jbossdb</connection-url>
<driver-class>org.gjt.mm.mysql.Driver</driver-class>
<user-name>root</user-name>
<password>abc</password>
</local-tx-datasource>
</datasources>
所以,Programmer数据表被创建在jbossdb数据库中,创建Programmer时还需要将BMP中的数据项转化为MySQL数据库的数据类型,而且需要依据BMP的设计为数据表指定主键,且主键不能为空。
具体的MySQL指令如下:
create table programmer(
name varchar(30) not null primary key,age int,lanague varchar(30),tool varchar(30)
);
MySQL命令行工具的操作方式如图3-24所示。
图3-24 MySQL命令行操作
下面是编写的客户端程序:
package com.liuyang.bmp.programmer.client;
import java.util.Collection;
import java.util.Iterator;
import javax.naming.InitialContext;
import com.liuyang.bmp.programmer.ProgrammerBMP;
import com.liuyang.bmp.programmer.ProgrammerBMPHome;
public class ProgrammerClient {
public static void main(String[] args) throws Exception {
Object ref = new InitialContext().lookup("ejb/ProgrammerBMP");
ProgrammerBMPHome home = (ProgrammerBMPHome)ref;
ProgrammerBMP liuyang = home.create("liuyang",25,"java","jboss");
ProgrammerBMP weifei = home.create("weifei",25,"java","eclipse");
Collection all = home.findByLanguage("java");
if((all!=null)&&(!all.isEmpty())){
Iterator it = all.iterator();
while(it.hasNext()){
ProgrammerBMP bmp = (ProgrammerBMP) it.next();
System.out.println(bmp.getName()+":"+bmp.getTool());
}
}
}
}
运行ProgrammerClient可以得到下面的结果:
liuyang:jboss
weifei:eclipse
从这个示例中可以看到,BMP的开发与CMP相比要复杂一些,但是在熟练掌握数据库API的情况下,配合JBossIDE,也可以很方便地进行开发。
BMP可以根据实际的需求修改查询方法的实现,这是与CMP的本质区别。如何选择CMP与BMP,要看实际的需求情况,如果CMP可以满足需求,并且开发者的数据库编程经验也不是十分丰富的话,CMP是首选方案。因为在CMP部署之后,JBoss会利用容器内的数据库连接机制维护CMP的方法,这些由JBoss提供的连接的代码是JBoss产品不断改进的成果,有JBoos的品质保证。
|