CSDN博客

img rgbwoo

借助JBuilder开发多种关联的Entity Bean

发表于2004/9/13 22:39:00  738人阅读

要开发多种关联的Entity Bean,首先就要了解Entity Bean中定义的几种映射关系,它们分别是One to One、One to many、many to many。下面就这几个关系作一简单描述。

假设有表student及表course,表student的主键为studentNo,表course的主键为courseNo。

student course (建立student到course的关联,单向)
course instance getCourse one to one
course Collection getCourse one to many
course Collection getCourse many to many

one to one的算法:
course表结构中有一个指向student主键的外键,当将student表中某条记录选取后,以该记录的主键值为索引,将course表中外键为该主键值的记录选出(仅仅有一条符合查询条件的记录),作为Course bean的一个实例返回。

one to many的算法:
course表结构中有一个指向student主键的外键,当将student表中某条记录选取后,以该记录的主键值为索引,将course表中外键为该主键值的记录选出(有多条满足查询条件的记录),作为一个包含有多个Course bean实例的Collection返回。

many to many的含义:
many to many其实是这样组成的-one to many, many to one。即course表结构中有一个指向student主键的外键,student表每条记录中的主键值作为索引的话,在course表中可选出多条记录;另一方面,student表结构中也有一个指向course主键的外键,course表每条记录中的主键值作为索引的话,在student表中可选出多条记录。

many to many的算法:
course表结构中有一个指向student主键的外键,当将student表中某条记录选取后,以该记录的主键值为索引,将course表中外键为该主键值的记录选出(有多条满足查询条件的记录),作为一个包含有多个Course bean实例的Collection返回。

从以上的算法可以看出,在单向的情况下,one to many和many to many生成的代码是一样的。但实现上有所不同,many to many通常会借助一张辅助表student-course。student-course表同时包含有到student、course表主键的外键。

1、当从student表中选取某条记录后,以该记录的主键值为索引,在student-course表中查询,其结果必然是有多条满足查询条件的记录,这些记录中,指向student表主键的外键值是相等的(等于索引值),但指向course表主键的外键值是不相等的。

2、分别以这些不等的外键值为索引,到course表中查询其主键值等于student-course表外键值的记录,返回一个包含有多个Course bean的实例。

student course (建立student到course的关联,双向)
course instance getCourse one to one student instance getStudent
course Collection getCourse one to many student Collection getStudent
course Collection getCourse many to many student Collection getStudent

one to one的算法:
course表结构中有一个指向student主键的外键,当将student表中某条记录选取后,以该记录的主键值为索引,将course表中外键为该主键值的记录选出(仅仅有一条符合查询条件的记录),作为Course bean的一个实例返回。 当将course表中某条记录选取后,以该记录的外键值为索引,将student表中主键为该外键值的记录选出(仅仅有一条符合查询条件的记录),作为Student bean的一个实例返回。

one to many的算法:
course表结构中有一个指向student主键的外键,当将student表中某条记录选取后,以该记录的主键值为索引,将course表中外键为该主键值的记录选出(有多条满足查询条件的记录),作为一个包含有多个Course bean实例的Collection返回。

当将course表中某条记录选取后,以该记录的外键值为索引,将student表中主键为该外键值的记录选出(仅仅有一条符合查询条件的记录),作为Student bean的一个实例返回。

many to many的算法:
course表结构中有一个指向student主键的外键,当将student表中某条记录选取后,以该记录的主键值为索引,将course表中外键为该主键值的记录选出(有多条满足查询条件的记录),作为一个包含有多个Course bean实例的Collection返回。

当将course表中某条记录选取后,以该记录的主键值为索引,将student表中外键为该主键值的记录选出(有多条满足查询条件的记录),作为一个包含有多个Student bean实例的Collection返回。

从以上算法可看出,在双向的情况下,one to one、one to many、many to many的双向算法极其类似。在实现上,many to many仍然会借用一张辅助表student-course。


在JBuilder6下建立一个名为Relationship的项目,在该项目中,我们将演示如何在entity bean之间实现各种关联。

从JDataStore数据库中导入Database schema,注意JNDI Name定义成dataSource-JdataStorePool。

在项目中加入三个entity bean,分别是Addresses、Employee、Project,暂时先不要生成AddressesRecord、EmployeeRecord、ProjectRecord等类。

Addresses的主键是firstName
Employee的主键是empNo
Project的主键是projId

我们在Addresses与Employee之间建立one to many(one to one与之类似)之间的双向关联:

选中Addresses,右键选择Create Relationship,随后出现一个箭头,鼠标拖向箭头指到Employee。完成后,JBuilder6自动命名该关联名称为employee。鼠标左键点击employee这个关联,对其属性进行编辑,最终属性如下:

同时需要重新指定字段间的Relationship,鼠标单击Edit RDMS Relationship按钮,将Addresses的FIRST_NAME(主键)与Employee的FIRST_NAME(外键)对应起来。

完成后的关联图类似如下:(箭头是双向的)

接下来,我们定义Employee与Project之间的many to many关联。

鼠标选中Employee,右键选择Create Relationship,拖曳箭头指向Project,然后修改生成的project关联属性:


重新设定字段间的关联,EMPLOYEE_PROJECT是业已存在的表,它的两个字段被定义成外键,分别指向EMPLOYEE的EMP_NO和PROJECT的PROJ_ID:

设定完成后,如下图所示:

一旦关联确定下来后,我们就要修改实现类,增加AddressesRecord、EmployeeRecord、ProjectRecord三个类。

对于AddressesRecord,由于AddressesBean有返回Collection类型的方法,因此,AddressesRecord应该增加一个Collection型的变量:

public class AddressesRecord implements Serializable
{

public String FIRST_NAME;
public String LAST_NAME;
public String ADDRESS;
public String CITY;
public String STATE;
public String ZIP;
public Collection EMPLOYEE_COLLECTION;

public AddressesRecord()
{
}
}

右键选择"Add New Method",为接口增加存取整条记录的2个方法:readRecord用于获取整条记录,writeRecord用于更新整条记录。注意,不要取名为getRecord和setRecord,JBuilder6在处理名为getXXX和setXXX的方法时,会将setXXX或getXXX从Addresses/AddressesRemote接口中去掉(估计get和set开头的方法使JBuilder6引起混淆)。

readRecord和writeRecord中由于分别要调用getEmployee和setEmployee方法,而这两个方法在EJB 2.0中只能被声明为local的,可见前面的图。所以,readRecord和writeRecord也必须定义为local,而不是local/remote:

需要自己实现readRecord、writeRecord方法,代码如下:

public relationship.AddressesRecord readRecord()
{
AddressesRecord addressesRecord = new AddressesRecord();
addressesRecord.FIRST_NAME = getFirstName();
addressesRecord.LAST_NAME = getLastName();
addressesRecord.ADDRESS = getAddress();
addressesRecord.CITY = getCity();
addressesRecord.STATE = getState();
addressesRecord.ZIP = getZip();
addressesRecord.EMPLOYEE_COLLECTION = getEmployee();
return addressesRecord;
}

public void writeRecord(relationship.AddressesRecord addressesRecord)
{
//设置主键值的setXXX方法只允许在ejbCreate中被调用!!!!
//setFirstName(addressesRecord.FIRST_NAME);
setLastName(addressesRecord.LAST_NAME);
setAddress(addressesRecord.ADDRESS);
setCity(addressesRecord.CITY);
setState(addressesRecord.STATE);
setZip(addressesRecord.ZIP);
setEmployee(addressesRecord.EMPLOYEE_COLLECTION);
}

以此类推,EmployeeRecord中增加Addresses型变量和Collection型变量;ProjectRecord中增加Collection型变量。并分别实现其readRecord、writeRecord方法。

由于返回的Collection类型只能用于local interface调用,因此我们必须使用和EJB共享同一个CLASSPATH的JSP或Servlet来调用,而不能使用独立的客户应用程序。

生成一个用于测试AddressesBean的Servlet服务小程序anson.relationship.AddressesTestServlet1,并加入以下变量:

private AddressesHome addressesHome = null;
private Addresses addresses = null;

到目前为止,项目中含有以下的库:

Relationship.ejbgrpx的属性中,项目中的库在Relationship.jar生成时都不加入,因为JBuilder6在Run或Debug那些EJB JAR时,会自动将这些库加入到weblogic.Server的启动路径中。

但是Default WebApp的Default.jar生成时,这些库大部分都加入,因为JBuilder6在部署WAR时,不会自动将这些库加入到weblogic.Server的启动路径中。

但实际上某些库不一定是需要的,因此可手工将其排除在外(排除WebLogic 6.x Client后,rebuild Default WebApp时可减少大量时间!),同时将生成的.war文件名从Default.war改为Relationship.war:



加SHTML Handler的原因是可使得我们通过如下的URL http://localhost:7001/AddressesTestServlet1.shtml来访问AddressesTestServlet1.shtml,该页面有一个到http://localhost:7001/addressestestservlet1的链接,点击该链接与直接在URL栏键入http://localhost:7001/addressestestservlet1的效果是一样的。

现在可测试AddressesTestServlet1.java这个Servlet是否可运行成功,鼠标右键点击AddressesTestServlet1,选择Web Run。

如果提示找不到anson.relationship.AddressesTestServlet1这个类,则检查Relationship.war这个文件的WEB_INF/classes下是否包含这个文件,如没有,则重新build Default WebApp这个应用。

在weblogic.Server的启动过程中,也许会报下面的错误:
<2002-4-4 下午10时13分00秒> <Error> <JDBC> <Cannot startup connection pool "JDataStorePool" Cannot load driver class: com.borland.datastore.jdbc.DataStoreDriver>
<2002-4-4 下午10时13分00秒> <Error> <JDBC> <Error during Data Source creation: weblogic.common.ResourceException: DataSource(dataSource-JDataStorePool) can't be created with non-existent Pool (connection or multi) (JDataStorePool)>

不必理会,这是由于在Deploy Relationship.war这个Web应用时,weblogic.Server启动的classpath中没有jds.jar文件,而在config.xml中配置了到JDataStore的数据库连接池。

至此,我们已经建立好了一个EJB JAR文件和一个WEB应用WAR文件,下面,我们将把JAR和WAR文件打包到一个EAR文件。

在项目中加入一个EAR,该选项在Enterprise属性页当中,下面是该EAR的一些属性:



鼠标右键点击RelationshipWrap.eargrp,选择Rebuild,将产生一个RelationshipWrap.ear文件。再次鼠标右键点击RelationshipWrap.eargrp,选择Run,JBuilder6将启动weblogic.Server,并把RelationshipWrap.ear部署到WebLogic上。JBuilder6在启动weblogic.Server时,会自动把项目中的所有库加到weblogic.Server启动的classpath中。

RelationshipWrap正常部署后,通过浏览器可访问AddressesTestServlet1这个Servlet,URL如下:

http://localhost:7001/Relationship/addressestestservlet1或
http://localhost:7001/Relationship/AddressesTestServlet1.shtml

下面,我们进一步修改AddressTestServlet1.java代码,来使用local interface中的方法从Addresses这个entity bean中存取数据库的数据。

AddressesTestServlet1.java修改后的代码如下:

import javax.naming.*;
import javax.rmi.*;
import javax.transaction.*;

public class AddressesTestServlet1 extends HttpServlet
{
private static final String CONTENT_TYPE = "text/html; charset=GBK";
private static final String ONE_TAB_CHAR = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
private static final String TWO_TAB_CHAR = ONE_TAB_CHAR + ONE_TAB_CHAR;
private AddressesHome addressesHome = null;
private Addresses addresses = null;

//Initialize global variables
public void init() throws ServletException
{
}
//Process the HTTP Get request
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>AddressesTestServlet1</title></head>");
out.println("<body>");
out.println("<p>The servlet has received a GET. This is the reply.</p>");

try
{
//get naming context
Context ctx = new InitialContext();
//look up jndi name
Object ref = ctx.lookup("Addresses");
//cast to Home interface
addressesHome = (AddressesHome) PortableRemoteObject.narrow(ref, AddressesHome.class);
//必须要开始一个事务!
UserTransaction tx = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");
//设置事务的超时时间,调试时使用,发布时可注释掉
tx.setTransactionTimeout(100000);
tx.begin();

//a instance
addresses = addressesHome.findByPrimaryKey("John");
//read record
AddressesRecord addressesRecord = addresses.readRecord();

System.out.println("The queried employee first name is " + addressesRecord.FIRST_NAME + " in table addresses.");
out.println("<p>The queried employee first name is " + addressesRecord.FIRST_NAME + " in table addresses.</p>");

Collection employees = addressesRecord.EMPLOYEE_COLLECTION;

System.out.println("/tThere're " + employees.size() + " employee(s)'s first name is " + addressesRecord.FIRST_NAME + " in table employee.");
out.println("<p>" + ONE_TAB_CHAR + "There're " + employees.size() + " employee(s)'s first name is " + addressesRecord.FIRST_NAME + " in table employee.</p>");

Iterator i = employees.iterator();
while (i.hasNext())
{
Employee emp = (Employee)i.next();
System.out.println("/tEmp No. is " + emp.getEmpNo() + " in table employee.");
out.println("<p>" + ONE_TAB_CHAR + "Emp No. is " + emp.getEmpNo() + " in table employee.</p>");

Collection projects = emp.getProject();
Iterator ii = projects.iterator();
while (ii.hasNext())
{
Project pro = (Project)ii.next();
System.out.println("/t/tProj Id is " + pro.getProjId() + ", Proj Name is " + pro.getProjName() + " in table project.");
out.println("<p>" + TWO_TAB_CHAR + "Proj Id is " + pro.getProjId() + ", Proj Name is " + pro.getProjName() + " in table project.</p>");
}
}

//待存取操作完成后,结束事务
tx.commit();
}
catch(Exception e)
{
e.printStackTrace();
out.println("<p>" + e.getMessage() + "</p>");
}
finally
{
out.println("</body></html>");
}
}
//Clean up resources
public void destroy()
{
}
}

将AddressesTestServlet1.java重新编译,再将RelationshipWrap.eargrp重新编译(先编译Relationship.jar,然后编译Relationship.war,最后编译RelationshipWrap.ear),产生新的RelationshipWrap.ear文件。

在AddressesTestServlet1.java的doGet方法中设置合适的断点,然后鼠标右键点击RelationshipWrap.eargrp,选择Debug。

待WebLogic完全启动后,在浏览器的URL栏键入
http://localhost:7001/Relationship/addressestestservlet1
以启动调试进程。

其他两个EJB的测试客户的完成过程和AddressesTestServlet1.java类似。

重要事项:

1、调用EJB本地接口的JSP或Servlet所属的WAR必须与EJB JAR统一打包到EAR文件中去。当该EAR被部署到WebLogic,JSP或Servlet才能调用EJB的本地接口。如果WAR和JAR单独部署,JSP或Servlet将不能调用EJB的本地接口。

2、调用EJB本地接口时,通过JNDI获得一个对象引用时,传入的参数是在weblogic-ejb-jar.xml中<local-jndi-name>标签指定的。

3、调用EJB本地接口中的任意一个方法之前,必须开始一个事务,在存取操作完成后,结束该事务(即使在ejb-jar.xml中指定EJB不需要事务)。否则,WebLogic会报类似下面的异常:

javax.ejb.EJBException: Attempt to access a collection valued cmr-field outside the scope of a transaction.
at anson.relationship.Addresses_WebLogic_CMP_RDBMS_employee_Set.checkTransaction(Addresses_WebLogic_CMP_RDBMS_employee_Set.java:605)
at anson.relationship.Addresses_WebLogic_CMP_RDBMS_employee_Set.size(Addresses_WebLogic_CMP_RDBMS_employee_Set.java:509)
at java.util.ArrayList.<init>(ArrayList.java:119)
at anson.relationship.AddressesTestServlet1.doGet(AddressesTestServlet1.java:79)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:740)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at weblogic.servlet.internal.ServletStubImpl.invokeServlet(ServletStubImpl.java:263)
at weblogic.servlet.internal.ServletStubImpl.invokeServlet(ServletStubImpl.java:200)
at weblogic.servlet.internal.WebAppServletContext.invokeServlet(WebAppServletContext.java:2390)
at weblogic.servlet.internal.ServletRequestImpl.execute(ServletRequestImpl.java:1959)
at weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:137)
at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:120)

4、当调用Addresses EJB的getEmployee方法时,获得若干Employee接口的实例,这些实例有getAddresses、getProject方法,可调用这些方法获得其他关联EJB的实例。

5、无法控制相关联的Collection会返回多少条记录。

6、Default WebApp在打包生成WAR文件时,缺省的是包含所有EJB JAR生成的class文件,这对于EJB JAR与WAR分别部署时是需要的,但假如EJB JAR和WAR统一打包到EAR中,则WAR所包含的EJB class文件则显得多余,可定制Default WebApp的属性来解决该问题,如下图:


配置Default WebApp在打包时只包含指定的class和资源文件,然后按Add Classes…按钮,将可以增加指定的class和资源文件
0 0

相关博文

我的热门文章

img
取 消
img