五、使用EJB QL开发查询
5.1 什么是EJB QL
在关系数据库的操作中查询是经常使用的,主要是通过select语句完成的。Entity Bean作为代表数据库中数据的持久性组件也同样需要查询操作,即能够找到符合某一查询条件的Entity Bean的实例。Entity Bean的查询操作是通过定义finder()方法完成的。对于CMP,定义finder()方法仅仅是声明一个方法,指明finder()方法的参数,该参数通常与查询条件中的参数对应,真正完成查询的动作是由EJB容器完成的。EJB容器要读取部署描述文件ejb-jar.xml(在*.jar/META-INF里)中的<query>项,该项包含了与finder()方法相对应的查询语句。<query>项中的查询语句遵循的语法规范是EJB QL。
EJB QL的开发查询步骤如下:
1> 在Home接口中增加finder()方法,其参数为查询条件中用到的参数;
2> 在ejb-jar.xml文件的<query>项定义EJB QL语句。
EJB QL是EJB2.0新加入的特性,它实现了如何在Home接口中定义各种查找方法。它以SQL-92为基础,可以由容器自动编译,这使得Entity Bean具有更高的可移植性,并且容易部署。
EJB QL语句由select、where、orderby三个子句组成,其后两个子句是可选的。
EJB QL查询语句举例如下:
例1:
SELECT stu FROM Student AS stu WHERE stu.grade > 5
该查询语句的含义是查询grade>5的Student Bean实例。“Student”是抽象模式名(Abstract schema name),在ejb-jar.xml文件中<abstract schema name>项指定的名称。“stu”是Student的别名,引入别名的好处是可以引用所代表对象的字段。stu.grade表示Student的grade字段,称为路径表达式。
例2:
SELECT i FROM Student As i WHERE i.name = ?1
该查询语句的含义是查找名字与finder()方法中的第一个参数相同的Student Bean实例。
WHERE字句的使用说明:
★ 以?n代表相应的finder()方法的输入参数;
★ 字符串类型的值要用单引号括起来(如果值中有单引号,则用双引号代替)
WHERE语句中可以使用的表达式和运算符如下:
☆ +,-,*,/,=,<,<=,>=,>,<>,NOT,AND,OR
☆ between
☆ like
☆ in
☆ member of
☆ is null(is not null)
内置函数:
● CONCAT(String first,String second)
● SUBSTRING(String source,int start,int length)
● LOCATE(String source,String patter)
● LENGTH(String source)
5.2 查询方法开发实例
5.2.1 打开《Entity Bean的运用(一)》中建立的comSample工程。双击工程窗口中cmpSample.jpx/Cmp,在文档窗口的EJB Designer中用鼠标右键单击Student项,选择Add/Finder菜单项,创建一个finder()方法。
5.2.2 设置finder()方法的各种参数。
★ Finder Name: findByName (finder()方法的名字)
★ Return type: StudentRemote(返回对象的类型)
★ Input parameter: java.lang.String name (输入参数)
★ Query: SELECT i FROM Student As i WHERE i.stuname = ?1(完成该查询的EJB QL语句)
5.2.3 查看Student Entity Bean的Home接口(StudentHome.java)会发现增加了一个finder()方法,该方法的定义如下:
public Student findByName(String name) throws FinderException;
5.2.4右键单击工程窗口中cmpSample.jpx/Cmp,选择Rebuild命令重新打包。
5.2.5 启动Weblogic Server,然后右键单击工程窗口中cmpSample.jpx/Cmp,选择Deploy Options for "Cmp.jar"/Redeploy命令重新部署。
5.2.6 通过EJB Test Client Wizard新建一个客户端程序StudentTestClient2.java,因为EJB已经改变了,所以不能用先前创建的客户端程序。修改main()方法如下:
public static void main(String[] args)
{
StudentTestClient2 client = new StudentTestClient2();
client.studentRemote = client.findByName("student2");
System.out.println("The student2's id is " + client.getStuid());
}
该方法中通过findByName()方法查找名字为student2的Entity Bean实例,然后输出该Entity Bean对应的stuid值。
5.2.7 运行客户端程序,验证结果。
为了简单,仅使用了一个参数,并且返回的是一个Bean实例。如果查询的结果不唯一,那么需要设置返回值为一个集合(Collection)。
六、建立Entity Bean的关系
6.1 Entity Bean的关系描述
因为数据之间存在一定的关系,所以代表持久性数据的Entity Bean也应该有相应的关系。例如《Entity Bean运用(一)》中2.1节中创建的两个数据表student和locker,这两个代表学生和课桌的数据表是有关系的,二者通过lockerid相关联。那么作为代表这两个表的Entity Bean,student和locker也应该体现这种关系。Entity Bean的关系是通过在有关系的Entity Bean中建立关系字段完成的。
EJB2.0支持单向、双向关系,并且Entity Bean之间可以存在一对一(1:1),一对多(1:N)或多对多(N:N)的关系。
6.2 Entity Bean关系开发实例
6.2.1打开《Entity Bean的运用(一)》中建立的comSample工程。参考2.2.9建立一个与locker数据表对应的Entity Bean。
6.2.2 在文档窗口的EJB Designer中单击Locker项,将Interface设置为local/remote,其他项采用默认值。
6.2.3 在文档窗口的EJB Designer中右键单击Locker项,选择Add/Relationship菜单项,将方向箭头指向要建立关系的Entity Bean Student。
6.2.4 将方向箭头指向Student后,可以看到在Locker中增加了一个字段student,该字段是代表与Locker相应的Student,称为关系字段。单击关系字段,可以设置此关系的属性。各项含义如下:
★ Relationship name: 关系的名字。
★ Multiplicity: 选择是一对一、一对多还是多对多的关系。
★ Navigability: 方向性。选择是单向还是双向的关系。如果选择unidirectional,表示选择单向关系,那么只在Locker中建立代表Student的关系字段。如果选择bidirectional,表示建立双向的关系,那么还将在Student中建立代表Locker的关系字段。
★ Cascade delete: 设置是否级联删除。级联删除指的是删除一个Entity Bean时,是否相应删除有关系的Entity Bean。此项设置为true还是false取决于具体应用的实际情况。例如在代表班级的Entity Bean与代表班级同学的Student之间建立了关系,由于班级不存在了,其成员student就也没有存在的必要了,所以此项应设置为true。对于本例而言,此处设置为false。
★ db-cascade-delete: 与Cascade delete的含义类似,只不过这里设置的是Entity Bean所代表的数据记录的级联删除。
本例中设置Multiplicity为one to one(1:1),Navigability为unidirectional。其他项采用默认值。
6.2.5 单击“Relationship properties”界面中的Edit RDBMS Relation按钮,设置student和locker数据表的关联字段,即指定这两个表之间通过哪个字段对应起来。如果这两个表有名字相同的字段,例如student表中有一个名位lockerid的字段,locker表中如果也有名为lockerid的字段,那么Jbuilder会自认为这两个表是通过lockid字段关联的。但是本例的locker表中使用的字段不是lockerid,而是id,所以此处必须手工指定数据表的关联字段。
6.2.6 在“Weblogic RDBMS Relation Editor”界面中在id字段和lockerid字段之间用鼠标拉一条直线,表示这两个表通过字段id和lockerid关联起来。
6.2.7 在工程窗口单击查看Locker表的Bean类,可以看到其中增加了针对关系字段的存取操作的代码:
public abstract void setStudent(cmpsample.Student student);
public abstract cmpsample.Student getStudent();
相应的ejb-jar.xml和weblogic-cmp-rdbms-jar.xml文件中对关系的描述信息也添加了。
6.2.8 在文档窗口的EJB Designer中右键单击Locker项,选择Add/Method菜单项增加一个方法,该方法返回与Locker关联的student信息。
修改该方法的设置如下:
★ Method name: getStuInfo
★ Return type: String
★ Interface: remote
6.2.9 在文档窗口的EJB Designer中右键单击Locker项,选择Add/View Bean Source菜单项,修改getStuInfo()方法如下:
public String getStuInfo()
{
String t = null;
try
{
Student stuRemote = getStudent();
t = "student id = " + stuRemote.getStuid() + ";student name = " +
stuRemote.getStuname();
}
catch (Exception e)
{}
return t;
}
6.2.10 重新编译、生成jar文件,然后使用Redeploy命令将新生成的Cmp.jar文件部署到Weblogic Server上。
6.2.11 建立Locker的客户端程序LockerTestClient1.java。
6.2.12 修改main()方法如下所示:
public static void main(String[] args)
{
LockerTestClient1 client = new LockerTestClient1();
client.findByPrimaryKey(new BigDecimal(2));
client.getStuInfo();
}
该方法首先查找一个stuid为2的Student,然后调用getStuInfo()方法以获得该Student对应的Locker的信息。
6.2.13 运行该程序,验证结果。
七、通过Session Bean访问Entity Bean
在前面给出的Entity Bean实例都是通过java客户端程序直接访问Entity Bean(通过Entity Bean的远程接口),在实际的应用系统开发中通常不采用这种做法,经常采用的是这样一种做法:建立一个Session Bean,该Session Bean中封装对Entity Bean的访问。而且访问时通过Entity Bean的本地接口进行以提高性能,客户端不与Entity Bean直接打交道,客户端通过Session Bean完成对Entity Bean的操作。这中做法其实是遵循了一种设计模式:Facade设计模式。
7.1 EJB引用
在一个EJB中访问其他EJB,例如Session Bean facade要访问Entity Bean student,可以直接在facade中用“硬编码”实现,即在facade中通过Student的JNDI名字找到Home对象,然后获得对外接口(Remote接口或Local接口),完成方法调用。这种方式的缺点是显而易见的,因为这两个EJB被紧紧绑在一起,例如student有什么改动,例如改名字,那么facade就必须改代码(这也是为什么在《Entity Bean的运用(二)》中需要重新建立一个TestClient的原因)。
J2EE采用这样一种方式解决此问题:在facade的部署描述文件中定义一个EJB引用,该项描述了对另一个EJB的引用,在facade的代码中只使用EJB引用的名字,EJB引用的名字是不变的,但是它所引用的内容是可变的,参见以下代码:
<weblogic-enterprise-bean>
<ejb-name>facade</ejb-name>
<reference-descriptor>
<ejb-reference-descriptor>
<ejb-ref-name>student</ejb-ref-name>
<jndi-name>Student</jndi-name>
</ejb-reference-descriptor>
</reference-descriptor>
<jndi-name>facade</jndi-name>
</weblogic-enterprise-bean>
建立了EJB引用后,facade可以通过此引用获得要访问的EJB student的接口,参见以下代码:
Context context = new InitialContext();
Object ref = context.lookup("java:/comp/env/student");
studentHome = (StudentHome)ref;
在使用EJB引用时,作为lookup方法参数的完整的JNDI名字是“java:/comp/env/”+ EJB引用名,例如名为student的EJB引用,对应完整的JNDI名字是“java:/comp/env/student”。
7.2 EJB引用实例
7.2.1打开《Entity Bean的运用(一)》中建立的comSample工程,新建一个名为facade的Session Bean(可参看《SESSION BEAN篇》)。修改该Bean的如下信息:
★ Bean name: facade
★ Interface: remote
★ Session type: Stateless
7.2.2 在文档窗口“EJB Designer”中用鼠标右键单击facade,选择Open DD Editor菜单项。
7.2.3 在“DD Editor”界面中单击下方的EJB Local References选项卡。
7.2.4 在“EJB Local References”的设置界面中,单击Add按钮,然后在Name项后输入EJB引用的名字student,选中IsLink复选框(如果要引用的EJB是位于同一Jbuilder工程中则选种此项),Link项选择Student(要引用的EJB),其他项采用系统自动的设置即可。
7.2.5 在facade中增加一个方法getStuInfo(),该方法的功能是找到与参数(代表stuid)对应的Entity Bean Student,然后调用Student的方法getStuid()和getStuname()。该方法的属性如下:
★ Method name: getStuInfo
★ Return type: String
★ Input parameters: String stuid
★ Interface: remote
7.2.6 在facade的Bean类中加入访问Student的代码。要访问Student,首先要获得Student的Home对象,这可以通过EJB引用获得。获得Home对象这部分代码要放在facade的ejbCreate()方法和ejbActivate()方法中(即只要facade的实例一被建立或激活,就会获得Student的Home对象)。获得Student的Home对象后,在getStuInfo()方法中通过Home对象得到Student的本地接口(Local Interface),然后通过本地接口调用Student的方法。用以下代码替换facadeBean.java的代码:(灰色代码部分为手工添加的)
package cmpsample;
import java.math.*;
import javax.ejb.*;
import javax.naming.*;
public class facadeBean
implements SessionBean
{
SessionContext sessionContext;
public Student student = null;
private StudentHome studentHome = null;
public void ejbCreate() throws CreateException
{
try
{
Context context = new InitialContext();
Object ref = context.lookup("java:/comp/env/student");
studentHome = (StudentHome) ref;
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void ejbRemove()
{
/**@todo Complete this method*/
}
public void ejbActivate()
{
try
{
Context context = new InitialContext();
Object ref = context.lookup("java:/comp/env/student");
studentHome = (StudentHome) ref;
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void ejbPassivate()
{
/**@todo Complete this method*/
}
public void setSessionContext(SessionContext sessionContext)
{
this.sessionContext = sessionContext;
}
public String getStuInfo(String stuid)
{
String strStu = null;
try
{
student = studentHome.findByPrimaryKey(new BigDecimal(stuid));
strStu = "The studentID is " + student.getStuid() + " Name is " +
student.getStuname();
}
catch (Exception e)
{}
return strStu;
}
}
7.2.7 重新编译EJB module,生成Cmp.jar文件,并部署到Weblogic Server上。
7.2.8 新建facade的客户端程序facadeTestClient1.java,利用该客户端程序访问facade的getStuInfo()方法。修改该客户端程序的main()方法如下:
public static void main(String[] args)
{
facadeTestClient1 client = new facadeTestClient1();
client.create();
client.getStuInfo("2");
}
7.2.9 编译运行该客户端程序,验证结果。