40、企业级 Java Bean 持久化开发全解析

企业级Java Bean持久化开发全解析

企业级 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 的持久化开发技术,提高开发效率和代码质量。在实际开发中,开发者需要根据项目的具体情况进行选择和应用,不断探索和实践,以实现最佳的开发效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值