企业级 Java Bean 持久化开发全解析
1. 测试客户端执行结果
测试客户端对 Bean 方法进行调用,其执行结果如下:
-- Initializing bean access.
-- Succeeded initializing bean access.
-- Execution time: 2944 ms.
-- Calling findByPrimaryKey(5)
-- Succeeded: findByPrimaryKey(5)
-- Execution time: 1452 ms.
-- Return value from findByPrimaryKey(5):
➥Stub[repository_id=RMI:entitybeansample.EmployeeRemote:000000 ...
-- Calling getEmpNo()
-- Succeeded: getEmpNo()
-- Execution time: 40 ms.
-- Return value from getEmpNo(): 5.
这个测试客户端展示了对 Bean 方法的初始化、调用和结果返回的过程,通过记录执行时间,我们可以对方法的性能有一个初步的了解。
2. 开发 BMP Bean
之前我们开发了由容器处理所有持久化操作的 CMP Bean,现在来看看构建 BMP Bean 的过程,并将其与 CMP Bean 进行对比。对于 BMP Bean,我们需要为其编写适当的持久化代码,而不是依赖容器来完成这项任务。
从 EJB 1.1 到 EJB 2.0 规范的变化,大部分集中在容器管理的持久化方面,对 Bean 管理的持久化也有一些改变,主要包括:
- 引入本地接口:允许在 Bean 中使用细粒度对象,且不会影响性能。
- 主接口功能扩展:主接口不仅可以包含访问器、修改器和查找方法,还可以包含业务方法,这些业务方法可以引用一组实体而非单个实体。
Bean 管理的持久化需要实现特定的接口,该接口最终会映射到关系型 SQL,具体映射关系如下表所示:
| Method | SQL Statement |
| — | — |
| ejbCreate | INSERT |
| ejbFindByPrimaryKey | SELECT |
| ejbFindByLastName | SELECT |
| ejbFindInRange | SELECT |
| ejbLoad | SELECT |
| ejbRemove | DELETE |
| ejbStore | UPDATE |
为了便于对比,我们将使用之前构建 CMP Bean 时的 Employee Bean 来开发 BMP Bean。
3. 定义 BMP Bean 的接口
- 主接口 :主接口应与 CMP Bean 的主接口完全相同,这样 Bean 的使用者无需关心该 Bean 是 CMP 还是 BMP。示例代码如下:
package entitybeansample;
import javax.ejb.*;
import java.util.*;
public interface EmployeeBMPHome extends javax.ejb.EJBLocalHome {
public EmployeeBMP create(Short empNo) throws CreateException;
public EmployeeBMP findByPrimaryKey(Short empNo) throws FinderException;
}
- 远程接口 :远程接口也应保持一致,它提供了对所需细粒度属性的访问。示例代码如下:
package entitybeansample;
import javax.ejb.*;
import java.util.*;
import java.rmi.*;
import java.sql.*;
import java.math.*;
public interface EmployeeBMPRemote extends javax.ejb.EJBObject {
public Short getEmpNo() throws RemoteException;
public void setFirstName(String firstName) throws RemoteException;
public String getFirstName() throws RemoteException;
public void setLastName(String lastName) throws RemoteException;
public String getLastName() throws RemoteException;
public void setPhoneExt(String phoneExt) throws RemoteException;
public String getPhoneExt() throws RemoteException;
public void setHireDate(Timestamp hireDate) throws RemoteException;
public Timestamp getHireDate() throws RemoteException;
public void setDeptNo(String deptNo) throws RemoteException;
public String getDeptNo() throws RemoteException;
public void setJobCode(String jobCode) throws RemoteException;
public String getJobCode() throws RemoteException;
public void setJobGrade(Short jobGrade) throws RemoteException;
public Short getJobGrade() throws RemoteException;
public void setJobCountry(String jobCountry) throws RemoteException;
public String getJobCountry() throws RemoteException;
public void setSalary(BigDecimal salary) throws RemoteException;
public BigDecimal getSalary() throws RemoteException;
public void setFullName(String fullName) throws RemoteException;
public String getFullName() throws RemoteException;
}
4. 实现 BMP Bean
在实现 Bean 时,需要注意使用的异常。
javax.ejb
包中的异常总结如下表:
| Method Name | Exception It Throws | Reason for Throwing |
| — | — | — |
| ejbCreate | CreateException | An input parameter is invalid. |
| ejbFindByPrimary Key (and other finder methods that return a single object) | ObjectNotFoundException (subclass of FinderException) | The database row for the requested entity bean cannot be found. |
| ejbRemove | RemoveException | The entity bean’s row cannot be deleted from the database. |
| ejbLoad | NoSuchEntityException | The database row to be loaded cannot be found. |
| ejbStore | NoSuchEntityException | The database row to be updated cannot be found. |
| (all methods) | EJBException | A system problem has been encountered. |
下面是 Employee BMP Bean 的完整实现代码:
package entitybeansample;
import java.sql.*;
import javax.ejb.*;
import javax.naming.*;
import javax.sql.*;
public class EmployeeBMPBean implements EntityBean {
EntityContext entityContext;
java.lang.Short empNo;
java.lang.String firstName;
java.lang.String lastName;
java.lang.String phoneExt;
java.sql.Timestamp hireDate;
java.lang.String deptNo;
java.lang.String jobCode;
java.lang.Short jobGrade;
java.lang.String jobCountry;
java.math.BigDecimal salary;
java.lang.String fullName;
public java.lang.Short ejbCreate(java.lang.Short empNo)
throws CreateException {
setEmpNo(empNo);
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(
"INSERT INTO employee (empno)" +
"values (?)");
ps.setShort(1,empNo.shortValue());
ps.executeUpdate();
return empNo;
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new CreateException();
}finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
return null;
}
public void ejbPostCreate(java.lang.Short empNo) throws CreateException {
/**@todo Complete this method*/
}
public void ejbRemove() throws RemoveException {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE " +
"FROM EMPLOYEE WHERE empno = ?");
ps.setShort(1,getEmpNo().shortValue());
ps.executeUpdate();
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new RemoveException();
}finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
//Getters and Setters for all members
public void setEmpNo(java.lang.Short empNo) {
this.empNo = empNo;
}
public void setFirstName(java.lang.String firstName) {
this.firstName = firstName;
}
public void setLastName(java.lang.String lastName) {
this.lastName = lastName;
}
public void setPhoneExt(java.lang.String phoneExt) {
this.phoneExt = phoneExt;
}
public void setHireDate(java.sql.Timestamp hireDate) {
this.hireDate = hireDate;
}
public void setDeptNo(java.lang.String deptNo) {
this.deptNo = deptNo;
}
public void setJobCode(java.lang.String jobCode) {
this.jobCode = jobCode;
}
public void setJobGrade(java.lang.Short jobGrade) {
this.jobGrade = jobGrade;
}
public void setJobCountry(java.lang.String jobCountry) {
this.jobCountry = jobCountry;
}
public void setSalary(java.math.BigDecimal salary) {
this.salary = salary;
}
public void setFullName(java.lang.String fullName) {
this.fullName = fullName;
}
public java.lang.Short getEmpNo() {
return empNo;
}
public java.lang.String getFirstName() {
return firstName;
}
public java.lang.String getLastName() {
return lastName;
}
public java.lang.String getPhoneExt() {
return phoneExt;
}
public java.sql.Timestamp getHireDate() {
return hireDate;
}
public java.lang.String getDeptNo() {
return deptNo;
}
public java.lang.String getJobCode() {
return jobCode;
}
public java.lang.Short getJobGrade() {
return jobGrade;
}
public java.lang.String getJobCountry() {
return jobCountry;
}
public java.math.BigDecimal getSalary() {
return salary;
}
public java.lang.String getFullName() {
return fullName;
}
//Find an individual instance and return the primary key
public java.lang.Short ejbFindByPrimaryKey(java.lang.Short empNo)
throws FinderException {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id FROM EMPLOYEE" +
"WHERE empno = ?");
ps.setShort(1,empNo.shortValue());
ResultSet rs = ps.executeQuery();
if (!rs.next()){
throw new ObjectNotFoundException();
}
return empNo;
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new EJBException(ex);
} finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
return null;
}
//Load a single instance from the datasource
public void ejbLoad() {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(
"SELECT EmpNo,DeptNo,FirstName," +
"FullName,HireDate,JobCode,JobCountry,JobGrade,LastName," +
"PhoneExt,Salary " +
"FROM EMPLOYEE WHERE empno = ?");
ps.setShort(1,getEmpNo().shortValue());
ResultSet rs = ps.executeQuery();
if (!rs.next()){
throw new EJBException("Object not found!");
}
setDeptNo(rs.getString(2));
setFirstName(rs.getString(3));
setFullName(rs.getString(4));
setHireDate(rs.getTimestamp(5));
setJobCode(rs.getString(6));
setJobCountry(rs.getString(7));
setJobGrade(new java.lang.Short(rs.getShort(8)));
setLastName(rs.getString(9));
setPhoneExt(rs.getString(10));
setSalary(rs.getBigDecimal(11));
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new EJBException(ex);
} finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
//Pasivate data to the datasource
public void ejbStore() {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement("Update employee " +
"set DeptNo = ?, FirstName = ?, FullName = ?, HireDate = ?," +
"JobCode = ?, JobCountry = ?, JobGrade = ?, LastName = ?," +
"PhoneExt = ?, Salary = ? where empno = ?");
ps.setString(1,getDeptNo());
ps.setString(2,getFirstName());
ps.setString(3,getFirstName());
ps.setString(4,getFullName());
ps.setTimestamp(5,getHireDate());
ps.setString(6,getJobCode());
ps.setString(7,getJobCountry());
ps.setShort(8,getJobGrade().shortValue());
ps.setString(9,getLastName());
ps.setString(10,getPhoneExt());
ps.setBigDecimal(11,getSalary());
ps.setShort(12,empNo.shortValue());
ps.executeUpdate();
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new EJBException();
}finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void unsetEntityContext() {
this.entityContext = null;
}
public void setEntityContext(EntityContext entityContext) {
this.entityContext = entityContext;
}
}
5. 部署描述符
部署描述符与之前创建的容器管理持久化 Bean 略有不同,例如
<persistence-type>
从
Container
变为
Bean
。示例代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-JAR PUBLIC "-//Sun Microsystems, Inc.//
DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/dtd/ejb-JAR_2_0.dtd">
<ejb-JAR>
<enterprise-beans>
<entity>
<display-name>Employee1</display-name>
<ejb-name>EmployeeBMP</ejb-name>
<home>entitybeansample.EmployeeBMPRemoteHome</home>
<remote>entitybeansample.EmployeeBMPRemote</remote>
<local-home>entitybeansample.EmployeeBMPHome</local-home>
<local>entitybeansample.EmployeeBMP</local>
<ejb-class>entitybeansample.EmployeeBMPBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.Short</prim-key-class>
<reentrant>False</reentrant>
<abstract-schema-name>Employee1</abstract-schema-name>
</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>EmployeeBMP</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-JAR>
6. 部署和使用实体 Bean
- 部署 :部署 BMP Bean 与部署容器管理的持久化 Bean 没有区别,同样使用主接口来创建、查找和移除实体实例。
-
使用
:使用 Bean 管理的持久化或容器管理的持久化的好处在于,Bean 开发者可以灵活选择最适合持久化需求的实现方式。实际上,Bean 的客户端无需关心持久化方法,只需确保持久化操作能够完成。例如,使用测试容器管理持久化 Bean 的相同客户端,现在可以测试 Bean 管理的持久化 Bean,只需将客户端使用的
Employee实体 Bean 更改为EmployeeBMP。示例代码如下:
package entitybeansample;
import java.math.*;
import java.sql.*;
import javax.naming.*;
import javax.rmi.*;
public class EmployeeTestClient {
static final private String ERROR_NULL_REMOTE =
"Remote interface reference is null. It must be created by " +
"calling one of the Home interface methods first.";
static final private int MAX_OUTPUT_LINE_LENGTH = 100;
private boolean logging = true;
private EmployeeBMPRemoteHome employeeBMPRemoteHome = null;
private EmployeeBMPRemote employeeBMPRemote = null;
//Construct the EJB test client
public EmployeeTestClient() {
long startTime = 0;
if (logging) {
log("Initializing bean access.");
startTime = System.currentTimeMillis();
}
try {
//get naming context
Context ctx = new InitialContext();
//look up jndi name
Object ref = ctx.lookup("EmployeeBMPRemote");
//cast to Home interface
employeeBMPRemoteHome = (EmployeeBMPRemoteHome)
PortableRemoteObject.narrow(ref, EmployeeBMPRemoteHome.class);
if (logging) {
long endTime = System.currentTimeMillis();
log("Succeeded initializing bean access.");
log("Execution time: " + (endTime - startTime) + " ms.");
}
/* Test your component Interface */
this.findByPrimaryKey(new java.lang.Short("5"));
this.getEmpNo();
}
catch(Exception e) {
if (logging) {
log("Failed initializing bean access.");
}
e.printStackTrace();
}
}
//-----------------------------------------------------------------------
// Methods that use Home interface methods to generate a Remote
// interface reference
//-----------------------------------------------------------------------
public EmployeeBMPRemote create(Short empNo) {
long startTime = 0;
if (logging) {
log("Calling create(" + empNo + ")");
startTime = System.currentTimeMillis();
}
try {
employeeBMPRemote = employeeBMPRemoteHome.create(empNo);
if (logging) {
long endTime = System.currentTimeMillis();
log("Succeeded: create(" + empNo + ")");
log("Execution time: " + (endTime - startTime) + " ms.");
}
}
catch(Exception e) {
if (logging) {
log("Failed: create(" + empNo + ")");
}
e.printStackTrace();
}
if (logging) {
log("Return value from create(" + empNo + "): " +
employeeBMPRemote + ".");
}
return employeeBMPRemote;
}
public EmployeeBMPRemote findByPrimaryKey(Short empNo) {
long startTime = 0;
if (logging) {
log("Calling findByPrimaryKey(" + empNo + ")");
startTime = System.currentTimeMillis();
}
try {
employeeBMPRemote = employeeBMPRemoteHome.findByPrimaryKey(empNo);
if (logging) {
long endTime = System.currentTimeMillis();
log("Succeeded: findByPrimaryKey(" + empNo + ")");
log("Execution time: " + (endTime - startTime) + " ms.");
}
}
catch(Exception e) {
if (logging) {
log("Failed: findByPrimaryKey(" + empNo + ")");
}
e.printStackTrace();
}
if (logging) {
log("Return value from findByPrimaryKey(" + empNo + "): " +
employeeBMPRemote + ".");
}
return employeeBMPRemote;
}
//-----------------------------------------------------------------------
// Methods that use Remote interface methods to access data through the bean
//-----------------------------------------------------------------------
public Short getEmpNo() {
Short returnValue = null;
if (employeeBMPRemote == null) {
System.out.println("Error in getEmpNo(): " + ERROR_NULL_REMOTE);
return returnValue;
}
long startTime = 0;
if (logging) {
log("Calling getEmpNo()");
startTime = System.currentTimeMillis();
}
try {
returnValue = employeeBMPRemote.getEmpNo();
if (logging) {
long endTime = System.currentTimeMillis();
log("Succeeded: getEmpNo()");
log("Execution time: " + (endTime - startTime) + " ms.");
}
}
catch(Exception e) {
if (logging) {
log("Failed: getEmpNo()");
}
e.printStackTrace();
}
if (logging) {
log("Return value from getEmpNo(): " + returnValue + ".");
}
return returnValue;
}
//-----------------------------------------------------------------------
// Utility Methods
//-----------------------------------------------------------------------
private void log(String message) {
if (message == null) {
System.out.println("-- null");
return ;
}
if (message.length() > MAX_OUTPUT_LINE_LENGTH) {
System.out.println("-- " + message.substring(0, MAX_OUTPUT_LINE_LENGTH) +
" ...");
}
else {
System.out.println("-- " + message);
}
}
//Main method
public static void main(String[] args) {
EmployeeTestClient client = new EmployeeTestClient();
// Use the client object to call one of the Home interface wrappers
// above, to create a Remote interface reference to the bean.
// If the return value is of the Remote interface type, you can use it
// to access the remote interface methods. You can also just use the
// client object to call the Remote interface wrappers.
}
}
生成的测试客户端可以进行调整或修改,以测试实现。不过,测试客户端通常不是为了全面测试客户端或长期使用而设计的。如需长期测试工具,可使用 JBuilder 的测试用例向导。
7. 高级容器管理的持久化
在 EJB 1.1 规范下,容器管理的持久化 Bean 功能较弱,很多情况下,Bean 开发者很快就会超出 CMP 的能力范围,不得不转向使用 Bean 管理的持久化,甚至很多开发者干脆避免使用实体 Bean。EJB 2.0 迅速解决了这些问题,现在 Bean 开发者可以使用容器管理的持久化来满足大多数持久化需求,主要得益于以下四个特性:
- EJB 查询语言
- 查找方法
- 多个实体 Bean 的关系映射
- 主接口中实现的业务方法
7.1 企业 JavaBeans 查询语言(EJB QL)
EJB QL 规范定义了一种查询语言,用于具有容器管理持久化的实体 Bean 的查找方法中。它基于 SQL92 的一个子集,而不是开发全新的查询语言。EJB QL 允许在实体 Bean 的抽象模式中定义的关系上进行导航。EJB QL 查询将包含在实体 Bean 的部署描述符中,容器会将这些查询转换为底层数据存储的目标语言,例如对于 JDatastore,EJB QL 会转换为真正的 SQL。这种转换使得使用容器管理持久化的实体 Bean 具有可移植性,即代码不依赖于特定类型的数据存储或应用服务器供应商。
EJB QL 有一些限制:
- 不支持注释。
- 日期和时间值始终以毫秒为单位表示,并包含在 Java 的
long
类型中。
- 容器管理的持久化不支持继承,即不能比较不同类型的实体 Bean。
EJB QL 查询由三个组件组成:
SELECT
、
FROM
和
WHERE
。
SELECT
和
FROM
子句是 EJB QL 唯一必需的部分,
WHERE
子句是可选的。以下是一些 EJB QL 查询的示例:
-
SELECT OBJECT(e) FROM Employee e
:返回 Employee 实体中的所有员工实例。
-
SELECT DISTINCT OBJECT(e) FROM Employee e WHERE e.empNo = ?1
:返回由
empNo
指定的员工实例,通常用于通过主键或备用键查找实例。
-
SELECT DISTINCT OBJECT(e) FROM Employee e, IN (e.salaryHistory) as s WHERE s.Country = ?1
:通过定义的关系导航到相关的 Bean,返回特定国家的所有薪资历史记录。
-
SELECT DISTINCT OBJECT(e) FROM Employee e WHERE e.salary BETWEEN ?1 AND ?2
:返回薪资在给定范围内的员工。
EJB QL 中的表达式和函数如下表所示:
| Type | Description |
| — | — |
| Standard comparisons | 与 SQL 一样,有
=
,
>
,
>=
,
<
,
<=
, 和
<>
操作。 |
| Arithmetic |
+
,
-
,
*
,
/
|
| Logical |
NOT
,
AND
,
OR
|
| Between |
BETWEEN
表达式用于确定值是否在范围内。 |
| In |
IN
表达式用于确定值是否包含在给定集合中。 |
| Like |
LIKE
表达式用于评估字符串是否匹配模式,单字符通配符使用
_
,零个或多个字符的通配符使用
%
。 |
| Null |
NULL
比较表达式用于评估属性或集合是否为
null
,使用
EMPTY
表达式评估集合是否为空。 |
此外,EJB QL 还包括一些算术和字符串函数,如下表所示:
| Function Syntax | Description |
| — | — |
| CONCAT(String, String) | 连接两个字符串并返回结果。 |
| SUBSTRING(String, start, length) | 根据传入的起始位置和长度返回子字符串。 |
| LOCATE(String, String [, start]) | 在一个字符串中查找另一个字符串并返回其位置。 |
| LENGTH(String) | 返回字符串的长度。 |
| ABS(number) | 返回数字的绝对值。 |
| SQRT(double) | 返回数字的平方根。 |
7.2 查找方法
查找实体的能力是所有实体 Bean 的关键要求。在 EJB 1.1 规范中,查找方法的实现往往需要开发者转向使用 Bean 管理的持久化,而不是使用更简单的容器管理的持久化 Bean。通过在容器管理的持久化 Bean 中结合使用 EJB QL 和查找方法,可以满足大多数查找单个或多个实体的需求。
以 Employee 实体为例,添加一个更复杂的查找方法
findBySalaryRange
来查找薪资在特定范围内的员工,步骤如下:
1. 右键单击 Employee 实体,选择
Add
->
Finder
,打开查找器编辑器。
2. 添加完成任务所需的查找器属性。
3. 设置以下属性:
- Method name:
findBySalaryRange
- Return type:
java.util.Collection
- Input parameters:
int low, int high
- Home interface: 本地主接口
- Query:
SELECT OBJECT(e) FROM Employee e WHERE e.Salary BETWEEN ?1 AND ?2
4. 保存并编译项目。
部署描述符现在包含了这个查找方法的信息,示例如下:
<query>
<query-method>
<method-name>findBySalaryRange</method-name>
<method-params>
<method-param>int</method-param>
<method-param>int</method-param>
</method-params>
</query-method>
<ejb-ql>SELECT OBJECT(e) FROM Employee e WHERE e.Salary
BETWEEN ?1 AND ?2</ejb-ql>
</query>
当 Bean 部署到容器时,容器会将部署描述符中的 EJB QL 信息转换为 JDatastore 的标准 SQL。
7.3 关系映射
EJB 2.0 规范中引入的关系映射带来了巨大的改变,它不仅提供了管理关系的机制,还能够建模和实现具有单向和双向遍历的复杂业务关系。每个实体 Bean 通常不是孤立的,它与系统中的其他实体相关联。例如,我们一直在使用的 Employee 实体与系统中的许多其他实体相关,如薪资历史记录。下面分别介绍一对一和多对多关系的创建过程。
7.3.1 一对一关系
以员工和薪资历史记录的关系为例,使用 JBuilder 提供的示例数据库,每个员工都有一个详细的薪资历史记录。在 JBuilder 中创建这种一对一关系的过程如下:
1. 展开
EmployeeData
数据源,验证
SALARY_HISTORY
表是否包含在数据源定义中。
2. 右键单击
SALARY_HISTORY
表,选择
Create Entity Bean
,确保版本属性设置为 EJB 2.0 兼容。
3. 右键单击 Employee 实体,选择
Add
->
Relationship
,在 EJB 设计器中会出现一个用于图形化表示关系的箭头。
4. 将关系箭头的末端拖到
SalaryHistory
实体上。
5. 选择新创建的包含关系的方法,JBuilder 的 EJB 设计器分配的名称为
salaryHistory
。
6. 打开关系属性编辑器,设置
Multiplicity
为
one-to-many
,
Navigability
为
unidirectional
(因为通过
SalaryHistory
实体访问 Employee 实体信息没有意义)。
7. 点击
Edit Table Reference
按钮,打开表引用编辑器,通过该编辑器提供两个实体之间的链接/键。
8. 双击 Employee 实体中的
empNo
,将其拖到
SalaryHistory
实体的
empNo
上,然后点击
OK
。
9. 确保 Employee 实体中
salaryHistory
方法的返回类型为
java.util.Collection
。
7.3.2 多对多关系
多对多关系在关系数据库设计中经常出现,通常通过解析表来实现,因为大多数关系数据库服务器不直接支持多对多关系,尽管对象建模对这种关系支持良好。以员工和项目的多对多关系为例,使用现有的
employeeData
数据源创建这种关系的步骤如下:
1. 右键单击 EJB 设计器中定义的数据模块内的
PROJECT
表,选择
Create CMP 2.0 Entity Bean
。
2. 右键单击 Employee 实体,选择
Add
->
Relationship
,将关系的另一端拖到
Project
实体上,创建 Employee 和 Project 实体 Bean 之间的多对多关系。
3. 选择新创建的关系实体
Project
,打开关系编辑器,设置以下属性:
- Relationship name:
employee-project
- Multiplicity:
many to many
- Navigability:
bidirectional
- Field name:
project
- Return type:
java.util.Collection
- Getters/setters: 本地
4. 点击
Edit Table Reference
,打开表引用编辑器。
5. 点击
Add Cross Table
,选择
EMPLOYEE_PROJECT
表作为交叉表。
6. 将
EMPLOYEE EMP_NO
拖到
EMPLOYEE_PROJECT EMP_NO
,将
EMPLOYEE_PROJECT PROJ_ID
拖到
PROJECT PROJ_ID
。
7. 点击
OK
,编译并保存项目。
7.4 主接口业务方法
EJB 规范的一个新变化是在主接口中添加和支持集合方法和业务方法。例如,计算所有员工的当前总薪资,构建过程如下:
1. 向 Employee 实体 Bean 添加一个新方法
calculateTotalSalary
。
2. 设置返回类型为
java.math.BigDecimal
,目标接口为本地主接口。
3. 创建另一个查找方法
findAll
,参数如下:
- Finder name:
findAll
- Return type:
java.util.Collection
- Parameter: 留空
- Method interface: 本地主接口
- Query:
SELECT OBJECT(e) FROM Employee AS e
综上所述,通过 BMP Bean 和高级容器管理的持久化技术,开发者可以更灵活地实现实体 Bean 的持久化需求,同时利用 EJB QL、查找方法、关系映射和主接口业务方法等特性,提高开发效率和代码的可维护性。
企业级 Java Bean 持久化开发全解析
8. 总结与展望
企业级 Java Bean 的持久化开发在 EJB 2.0 规范下得到了极大的改进和完善。从 BMP Bean 到高级容器管理的持久化,开发者拥有了更多的选择和工具来满足不同的持久化需求。
BMP Bean 为开发者提供了手动控制持久化代码的能力,适用于需要精细控制数据存储和操作的场景。通过实现特定的接口和编写 SQL 语句,开发者可以直接管理实体 Bean 的创建、查找、删除和更新操作。
而高级容器管理的持久化则借助 EJB QL、查找方法、关系映射和主接口业务方法等特性,使得开发者能够更高效地实现复杂的持久化需求。EJB QL 提供了一种与底层数据存储无关的查询语言,增强了代码的可移植性;查找方法允许快速定位实体;关系映射则支持建模和实现复杂的业务关系;主接口业务方法则为业务逻辑的实现提供了便利。
在未来的开发中,随着技术的不断发展,企业级 Java Bean 的持久化开发可能会朝着更加智能化、自动化的方向发展。例如,可能会出现更强大的查询语言和工具,进一步简化开发过程;关系映射可能会支持更复杂的关系类型和操作;主接口业务方法可能会与更多的业务服务进行集成,提供更全面的功能。
同时,开发者也需要不断学习和掌握新的技术和规范,以适应不断变化的开发需求。在选择持久化方式时,需要根据项目的具体情况进行权衡和选择,以达到最佳的开发效果。
9. 常见问题解答
在企业级 Java Bean 持久化开发过程中,开发者可能会遇到一些常见的问题,以下是对这些问题的解答:
9.1 BMP Bean 和 CMP Bean 如何选择?
- BMP Bean :适用于需要精细控制数据存储和操作的场景。当业务逻辑复杂,需要手动编写 SQL 语句来实现特定的持久化需求时,BMP Bean 是一个不错的选择。例如,在处理复杂的事务逻辑或需要与特定数据库进行交互时,BMP Bean 可以提供更大的灵活性。
- CMP Bean :适用于业务逻辑相对简单,对持久化操作的控制要求不高的场景。CMP Bean 由容器自动处理持久化操作,开发者只需要关注业务逻辑的实现,无需编写大量的 SQL 语句,从而提高了开发效率。
9.2 EJB QL 与 SQL 有什么区别?
- 语法 :EJB QL 基于 SQL92 的一个子集,语法与 SQL 有相似之处,但也有一些差异。例如,EJB QL 主要用于实体 Bean 的查找方法中,其查询对象是实体 Bean 的抽象模式,而不是具体的数据库表。
- 可移植性 :EJB QL 具有更好的可移植性,因为它会由容器自动转换为底层数据存储的目标语言。这意味着使用 EJB QL 编写的代码可以在不同的数据库和应用服务器上运行,而无需进行大量的修改。
- 功能范围 :EJB QL 的功能范围相对较窄,它主要用于实体 Bean 的查找操作,而 SQL 则可以进行更广泛的数据库操作,如数据的插入、更新和删除等。
9.3 关系映射中如何处理复杂的业务关系?
- 使用合适的关系类型 :根据业务需求选择合适的关系类型,如一对一、一对多、多对多等。在 JBuilder 等开发工具中,可以通过可视化的方式创建和配置这些关系。
- 定义关系属性 :在创建关系时,需要定义关系的属性,如多重性、导航性等。这些属性决定了关系的行为和操作方式。
- 使用中间表 :对于多对多关系,通常需要使用中间表来实现。在 JBuilder 中,可以通过添加交叉表的方式来处理多对多关系。
9.4 主接口业务方法有什么作用?
- 封装业务逻辑 :主接口业务方法可以将复杂的业务逻辑封装在一个方法中,提高代码的可维护性和复用性。例如,计算所有员工的总薪资可以通过主接口业务方法来实现,而不需要在多个地方重复编写计算逻辑。
- 提供统一的接口 :主接口业务方法为客户端提供了一个统一的接口,客户端可以通过调用这些方法来完成特定的业务操作,而不需要了解具体的实现细节。
- 支持事务处理 :主接口业务方法可以与容器的事务管理机制集成,确保业务操作的原子性和一致性。
10. 参考代码示例汇总
为了方便开发者参考,以下汇总了本文中涉及的主要代码示例:
10.1 BMP Bean 相关代码
主接口
package entitybeansample;
import javax.ejb.*;
import java.util.*;
public interface EmployeeBMPHome extends javax.ejb.EJBLocalHome {
public EmployeeBMP create(Short empNo) throws CreateException;
public EmployeeBMP findByPrimaryKey(Short empNo) throws FinderException;
}
远程接口
package entitybeansample;
import javax.ejb.*;
import java.util.*;
import java.rmi.*;
import java.sql.*;
import java.math.*;
public interface EmployeeBMPRemote extends javax.ejb.EJBObject {
public Short getEmpNo() throws RemoteException;
public void setFirstName(String firstName) throws RemoteException;
public String getFirstName() throws RemoteException;
public void setLastName(String lastName) throws RemoteException;
public String getLastName() throws RemoteException;
public void setPhoneExt(String phoneExt) throws RemoteException;
public String getPhoneExt() throws RemoteException;
public void setHireDate(Timestamp hireDate) throws RemoteException;
public Timestamp getHireDate() throws RemoteException;
public void setDeptNo(String deptNo) throws RemoteException;
public String getDeptNo() throws RemoteException;
public void setJobCode(String jobCode) throws RemoteException;
public String getJobCode() throws RemoteException;
public void setJobGrade(Short jobGrade) throws RemoteException;
public Short getJobGrade() throws RemoteException;
public void setJobCountry(String jobCountry) throws RemoteException;
public String getJobCountry() throws RemoteException;
public void setSalary(BigDecimal salary) throws RemoteException;
public BigDecimal getSalary() throws RemoteException;
public void setFullName(String fullName) throws RemoteException;
public String getFullName() throws RemoteException;
}
实现类
package entitybeansample;
import java.sql.*;
import javax.ejb.*;
import javax.naming.*;
import javax.sql.*;
public class EmployeeBMPBean implements EntityBean {
EntityContext entityContext;
java.lang.Short empNo;
java.lang.String firstName;
java.lang.String lastName;
java.lang.String phoneExt;
java.sql.Timestamp hireDate;
java.lang.String deptNo;
java.lang.String jobCode;
java.lang.Short jobGrade;
java.lang.String jobCountry;
java.math.BigDecimal salary;
java.lang.String fullName;
public java.lang.Short ejbCreate(java.lang.Short empNo)
throws CreateException {
setEmpNo(empNo);
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(
"INSERT INTO employee (empno)" +
"values (?)");
ps.setShort(1,empNo.shortValue());
ps.executeUpdate();
return empNo;
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new CreateException();
}finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
return null;
}
public void ejbPostCreate(java.lang.Short empNo) throws CreateException {
/**@todo Complete this method*/
}
public void ejbRemove() throws RemoveException {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE " +
"FROM EMPLOYEE WHERE empno = ?");
ps.setShort(1,getEmpNo().shortValue());
ps.executeUpdate();
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new RemoveException();
}finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
//Getters and Setters for all members
public void setEmpNo(java.lang.Short empNo) {
this.empNo = empNo;
}
public void setFirstName(java.lang.String firstName) {
this.firstName = firstName;
}
public void setLastName(java.lang.String lastName) {
this.lastName = lastName;
}
public void setPhoneExt(java.lang.String phoneExt) {
this.phoneExt = phoneExt;
}
public void setHireDate(java.sql.Timestamp hireDate) {
this.hireDate = hireDate;
}
public void setDeptNo(java.lang.String deptNo) {
this.deptNo = deptNo;
}
public void setJobCode(java.lang.String jobCode) {
this.jobCode = jobCode;
}
public void setJobGrade(java.lang.Short jobGrade) {
this.jobGrade = jobGrade;
}
public void setJobCountry(java.lang.String jobCountry) {
this.jobCountry = jobCountry;
}
public void setSalary(java.math.BigDecimal salary) {
this.salary = salary;
}
public void setFullName(java.lang.String fullName) {
this.fullName = fullName;
}
public java.lang.Short getEmpNo() {
return empNo;
}
public java.lang.String getFirstName() {
return firstName;
}
public java.lang.String getLastName() {
return lastName;
}
public java.lang.String getPhoneExt() {
return phoneExt;
}
public java.sql.Timestamp getHireDate() {
return hireDate;
}
public java.lang.String getDeptNo() {
return deptNo;
}
public java.lang.String getJobCode() {
return jobCode;
}
public java.lang.Short getJobGrade() {
return jobGrade;
}
public java.lang.String getJobCountry() {
return jobCountry;
}
public java.math.BigDecimal getSalary() {
return salary;
}
public java.lang.String getFullName() {
return fullName;
}
//Find an individual instance and return the primary key
public java.lang.Short ejbFindByPrimaryKey(java.lang.Short empNo)
throws FinderException {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id FROM EMPLOYEE" +
"WHERE empno = ?");
ps.setShort(1,empNo.shortValue());
ResultSet rs = ps.executeQuery();
if (!rs.next()){
throw new ObjectNotFoundException();
}
return empNo;
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new EJBException(ex);
} finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
return null;
}
//Load a single instance from the datasource
public void ejbLoad() {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(
"SELECT EmpNo,DeptNo,FirstName," +
"FullName,HireDate,JobCode,JobCountry,JobGrade,LastName," +
"PhoneExt,Salary " +
"FROM EMPLOYEE WHERE empno = ?");
ps.setShort(1,getEmpNo().shortValue());
ResultSet rs = ps.executeQuery();
if (!rs.next()){
throw new EJBException("Object not found!");
}
setDeptNo(rs.getString(2));
setFirstName(rs.getString(3));
setFullName(rs.getString(4));
setHireDate(rs.getTimestamp(5));
setJobCode(rs.getString(6));
setJobCountry(rs.getString(7));
setJobGrade(new java.lang.Short(rs.getShort(8)));
setLastName(rs.getString(9));
setPhoneExt(rs.getString(10));
setSalary(rs.getBigDecimal(11));
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new EJBException(ex);
} finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
//Pasivate data to the datasource
public void ejbStore() {
Connection con = null;
try {
InitialContext initial = new InitialContext();
DataSource ds = (DataSource)initial.lookup(
"java:comp/env/jdbc/EmployeeData");
con = ds.getConnection();
PreparedStatement ps = con.prepareStatement("Update employee " +
"set DeptNo = ?, FirstName = ?, FullName = ?, HireDate = ?," +
"JobCode = ?, JobCountry = ?, JobGrade = ?, LastName = ?," +
"PhoneExt = ?, Salary = ? where empno = ?");
ps.setString(1,getDeptNo());
ps.setString(2,getFirstName());
ps.setString(3,getFirstName());
ps.setString(4,getFullName());
ps.setTimestamp(5,getHireDate());
ps.setString(6,getJobCode());
ps.setString(7,getJobCountry());
ps.setShort(8,getJobGrade().shortValue());
ps.setString(9,getLastName());
ps.setString(10,getPhoneExt());
ps.setBigDecimal(11,getSalary());
ps.setShort(12,empNo.shortValue());
ps.executeUpdate();
}
catch (SQLException ex) {
ex.printStackTrace();
}catch (NamingException ex) {
ex.printStackTrace();
throw new EJBException();
}finally {
if (con!=null){
try {
con.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void unsetEntityContext() {
this.entityContext = null;
}
public void setEntityContext(EntityContext entityContext) {
this.entityContext = entityContext;
}
}
部署描述符
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-JAR PUBLIC "-//Sun Microsystems, Inc.//
DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/dtd/ejb-JAR_2_0.dtd">
<ejb-JAR>
<enterprise-beans>
<entity>
<display-name>Employee1</display-name>
<ejb-name>EmployeeBMP</ejb-name>
<home>entitybeansample.EmployeeBMPRemoteHome</home>
<remote>entitybeansample.EmployeeBMPRemote</remote>
<local-home>entitybeansample.EmployeeBMPHome</local-home>
<local>entitybeansample.EmployeeBMP</local>
<ejb-class>entitybeansample.EmployeeBMPBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.Short</prim-key-class>
<reentrant>False</reentrant>
<abstract-schema-name>Employee1</abstract-schema-name>
</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>EmployeeBMP</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-JAR>
10.2 高级容器管理持久化相关代码
EJB QL 查找方法部署描述符示例
<query>
<query-method>
<method-name>findBySalaryRange</method-name>
<method-params>
<method-param>int</method-param>
<method-param>int</method-param>
</method-params>
</query-method>
<ejb-ql>SELECT OBJECT(e) FROM Employee e WHERE e.Salary
BETWEEN ?1 AND ?2</ejb-ql>
</query>
11. 结束语
企业级 Java Bean 的持久化开发是一个复杂而重要的领域,涉及到众多的技术和规范。通过本文的介绍,我们了解了 BMP Bean 和高级容器管理的持久化的相关知识,包括 EJB QL、查找方法、关系映射和主接口业务方法等。希望这些内容能够帮助开发者更好地掌握企业级 Java Bean 的持久化开发技术,提高开发效率和代码质量。在实际开发中,开发者需要根据项目的具体情况进行选择和应用,不断探索和实践,以实现最佳的开发效果。
企业级Java Bean持久化开发全解析
超级会员免费看
22

被折叠的 条评论
为什么被折叠?



