Oracle® Application Server Containers for J2EE Enterprise JavaBeans Developer's Guide
10g Release 2 (10.1.2) Part No. B15505-02 |
|
Previous |
Next |
If you want to implement the manual storing and reloading of data, then use a bean-managed persistent (BMP) bean. The container manages the data within callback methods, which you must implement. All the logic for storing data to your persistent storage is included in the ejbStore
method, and reloaded from your storage in the ejbLoad
method. The container invokes these methods when necessary.
This chapter demonstrates simple BMP EJB development with a basic configuration and deployment. Download the BMP entity bean example from the OC4J sample code page at on the OTN Web site.
The following sections discuss how to implement data persistence:
Chapter 3, "Implementing Session Beans" shows how to develop a stateless session bean. Chapter 4, "CMP Entity Beans" describes the extra steps necessary for implementing a CMP entity bean. In a CMP bean, the primary key and all functions for persistence are performed by the container; in a BMP bean, you must implement the primary key and all functions to save the persistence of your bean. The primary key is managed in the ejbCreate
method. The persistence is managed within the following functions:
Restoring the persistent data to the bean within your implementation of the ejbLoad
method.
Passivation of the bean instance within the ejbPassivate
method.
Activation of the passivated bean instance within the ejbActivate
method.
The following is a summary of the steps mentioned in Chapter 4, "CMP Entity Beans" that you must do when creating your bean. See Chapter 3, "Implementing Session Beans" and Chapter 4, "CMP Entity Beans" for further details. The rest of this chapter covers how you implement the primary key and the persistence functions.
Create the component interfaces for the bean. The component interfaces declare the methods that a client can invoke.
Create the home interfaces for the bean. The home interface defines the create
and finder methods, including findByPrimaryKey
, for your bean.
Define the primary key for the bean. The primary key identifies each entity bean instance and is a serializable class. You can use a simple data type class, such as java.lang.String
, or define a complex class, such as one with two or more objects as components of the primary key.
Implement the bean.
If the persistent data is saved to or restored from a database, you must ensure that the correct tables exist for the bean.
Create the bean deployment descriptor. The deployment descriptor specifies properties for the bean through XML elements.
Create an EJB JAR file containing the bean, component interface, home interface, and the deployment descriptors. Once created, configure the application.xml
file, create an EAR file, and deploy the EJB to OC4J.
Note: This book does not cover EJB container services. See the JTA, Data Source, and JNDI chapters in the Oracle Application Server Containers for J2EE Services Guide for more information. Since transactions are not covered in this chapter, the example BMP bean uses container-managed transactions.For security, see the Oracle Application Server Containers for J2EE Security Guide. |
The BMP entity bean definition of the component and home interfaces is identical to the CMP entity bean. For examples of how the component and home interfaces are implemented, see "Creating Entity Beans".
Because the container is not managing the primary key or the saving of the persistent data, the bean callback functions must include the implementation logic for these functions. The container invokes the ejbCreate
, ejbFindByPrimaryKey
, other finder methods, ejbStore
, and ejbLoad
methods when appropriate.
The following sections talk about how you add the implementation for managing your BMP bean:
The ejbCreate
method is responsible primarily for the creation of the primary key. This includes the following:
Creating the primary key.
Creating the persistent data representation for the key.
Initializing the key to a unique value and ensuring no duplication.
Returning this key to the container.
The container maps the key to the entity bean reference.
The following example shows the ejbCreate
method for the employee example, which initializes the primary key, empNo
. It should automatically generate a primary key that is the next available number in the employee number sequence. However, for this example to be simple, the ejbCreate
method requires that the user provide the unique employee number.
Note: All Try blocks within the samples have been removed in this discussion. However, the entire BMP entity bean example, including the Try blocks, is available on OTN from the OC4J sample code page athttp://www.oracle.com/technology/tech/java/oc4j/demos/ on the OTN Web site.
|
In addition, because the full data for the employee is provided within this method, the data is saved within the context variables of this instance. After initialization, it returns this key to the container.
// The create methods takes care of generating a new empNo and returns
// its primary key to the container
public Integer ejbCreate (Integer empNo, String empName, Float salary) throws CreateException
{
/* in this implementation, the client gives the employee number, so
only need to assign it, not create it. */
this.empNo = empNo;
this.empName = empName;
this.salary = salary;
/* insert employee into database */
conn = getConnection(dsName);
ps = conn.prepareStatement("INSERT INTO EMPLOYEEBEAN (EmpNo, EmpName, SAL)
VALUES ( "+this.empNo.intValue()+", "+this.empName+"," +this.salary.floatValue()+")");
ps.executeUpdate();
ps.close();
/* return the new primary key.*/
return (empNo);
}
The deployment descriptor defines only the primary key class in the <prim-key-class>
element. Because the bean is saving the data, there is no definition of persistence data in the deployment descriptor. Note that the deployment descriptor does define the database the bean uses in the <resource-ref>
element. For more information on database configuration, see "Modify XML Deployment Descriptors".
<enterprise-beans>
<entity>
<display-name>EmployeeBean</display-name>
<ejb-name>EmployeeBean</ejb-name>
<local-home>employee.EmployeeHome</local-home>
<local>employee.Employee</local>
<ejb-class>employee.EmployeeBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>False</reentrant>
<resource-ref>
<res-ref-name>jdbc/OracleDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
</entity>
</enterprise-beans>
Alternatively, you can create a complex primary key based on several data types. You define a complex primary key within its own class, as follows:
package employee; public class EmployeePK implements java.io.Serializable { public Integer empNo; public String empName; public Float salary; public EmployeePK(Integer empNo) { this.empNo = empNo; this.empName = null; this.salary = null; } public EmployeePK(Integer empNo, String empName, Float salary) { this.empNo = empNo; this.empName = empName; this.salary = salary; } }
For a primary key class, you define the class in the <prim-key-class>
element, which is the same for the simple primary key definition.
<enterprise-beans>
<entity>
<display-name>EmployeeBean</display-name>
<ejb-name>EmployeeBean</ejb-name>
<local-home>employee.EmployeeHome</local-home>
<local>employee.Employee</local>
<ejb-class>employee.EmployeeBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>employee.EmployeePK</prim-key-class>
<reentrant>False</reentrant>
<resource-ref>
<res-ref-name>jdbc/OracleDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
</entity>
</enterprise-beans>
The employee example requires that the employee number is given to the bean by the user. Another method would be to generate the employee number by computing the next available employee number, and use this in combination with the employee's name and office location.
After defining the complex primary key class, you would create your primary key within the ejbCreate
method, as follows:
public EmployeePK ejbCreate(Integer empNo, String empName, Float salary) throws CreateException { pk = new EmployeePK(empNo, empName, salary); ... }
The other task that the ejbCreate
(or ejbPostCreate
) should handle is allocating any resources necessary for the life of the bean. For this example, because we already have the information for the employee, the ejbCreate
performs the following:
Retrieves a connection to the database. This connection remains open for the life of the bean. It is used to update employee information within the database. It should be released in ejbPassivate
and ejbRemove
, and reallocated in ejbActivate
.
Updates the database with the employee information.
This is executed, as follows:
public EmployeePK ejbCreate(Integer empNo, String empName, Float salary) throws CreateException { pk = new EmployeePK(empNo, empName, salary); conn = getConnection(dsName); ps = conn.prepareStatement("INSERT INTO EMPLOYEEBEAN (EmpNo, EmpName, SAL) VALUES ( "+this.empNo.intValue()+", "+this.empName+"," +this.salary.floatValue()+")"); ps.executeUpdate(); ps.close(); return pk; }
The ejbFindByPrimaryKey
implementation is a requirement for all BMP entity beans. Its primary responsibility is to ensure that the primary key corresponds to a valid bean. Once it is validated, it returns the primary key to the container, which uses the key to return the bean reference to the user.
This sample verifies that the employee number is valid and returns the primary key, which is the employee number, to the container. A more complex verification would be necessary if the primary key was a class.
public Integer ejbFindByPrimaryKey(Integer empNoPK) throws FinderException { if (empNoPK == null) { throw new FinderException("Primary key cannot be null"); } ps = conn.prepareStatement("SELECT EMPNO FROM EMPLOYEEBEAN WHERE EMPNO = ?"); ps.setInt(1, empNoPK.intValue()); ps.executeQuery(); ResultSet rs = ps.getResultSet(); if (rs.next()) { /*PK is validated because it exists already*/ } else { throw new FinderException("Failed to select this PK"); } ps.close(); return empNoPK; }
You can create other finder methods in addition to the single ejbFindByPrimaryKey
.
To create other finder methods, do the following:
Add the finder method to the home interface.
Implement the finder method in the BMP bean implementation.
Finders can retrieve one or more beans according to the WHERE
clause. If more than a single bean is returned, then a Collection
of primary keys must be returned by the BMP finder method. These finder methods need only to gather the primary keys for all of the entity beans that should be returned to the user. The container maps the primary keys to references to each entity bean within either a Collection
(if multiple references are returned) or to the single class type.
The following example shows the implementation of a finder method that returns all employee records.
public Collection ejbFindAll() throws FinderException { Vector recs = new Vector(); ps = conn.prepareStatement("SELECT EMPNO FROM EMPLOYEEBEAN"); ps.executeQuery(); ResultSet rs = ps.getResultSet(); int i = 0; while (rs.next()) { retEmpNo = new Integer(rs.getInt(1)); recs.add(retEmpNo); } ps.close(); return recs; }
The container invokes the ejbStore
method when the persistent data should be saved to the database. This synchronizes the state of the instance to the entity in the underlying database. For example, the container invokes before the container passivates the bean instance or removes the instance. The BMP bean is responsible for ensuring that all data is stored to some resource, such as a database, within this method.
public void ejbStore() { //Container invokes this method to instruct the instance to //synchronize its state by storing it to the underlying database ps = conn.prepareStatement("UPDATE EMPLOYEEBEAN SET EMPNAME=?, SALARY=? WHERE EMPNO=?)"; ps.setString(1, this.empName); ps.setFloat(2, this.salary.floatValue()); ps.setInt(3, this.empNo.intValue()); if (ps.executeUpdate() != 1) { throw new EJBException("Failed to update record"); } ps.close(); }
The container invokes the ejbLoad
method whenever it needs to synchronize the state of the bean with what exists in the database. This method is invoked after activating the bean instance to refresh it with the state that is in the database. The purpose of this method is to repopulate the persistent data with the saved state. For most ejbLoad
methods, this implies reading the data from a database into the instance data variables.
public void ejbLoad() { //Container invokes this method to instruct the instance to //synchronize its state by loading it from the underlying database this.empNo = ctx.getPrimaryKey(); ps = conn.prepareStatement("SELECT EMP_NO, EMP_NAME, SALARY WHERE EMPNAME=?"); ps.setInt(1, this.empNo.intValue()); ps.executeQuery(); ResultSet rs = ps.getResultSet(); if (rs.next()) { this.empNo = new Integer(rs.getInt(1)); this.empName = new String(rs.getString(2)); this.salary = new Float(rs.getFloat(3)); } else { throw new FinderException("Failed to select this PK"); } ps.close(); }
The ejbPassivate
method is invoked directly before the bean instance is serialized for future use. It will be re-activated, through the ejbActivate
method, the next time the user invokes a method on this instance.
Before the bean is passivated, you should release all resources and release any static information that would be too large to be serialized. Any large, static information that can be easily regenerated within the ejbActivate
method should be released in this method.
In our example, the only resource that cannot be serialized is the open database connection. It is closed in this method and reopened in the ejbActivate
method.
public void ejbPassivate() { // Container invokes this method on an instance before the instance // becomes disassociated with a specific EJB object conn.close(); }
The container invokes this method when the bean instance is reactivated. That is, the user has asked to invoke a method on this instance. This method is used to open resources and rebuild static information that was released in the ejbPassivate
method.
Note: The container may call theejbActivate() method multiple times when the bean is associated with several wrappers.
|
In addition, the container invokes this method after the start of any transaction.
Our employee example opens the database connection where the employee information is stored.
public void ejbActivate() { // Container invokes this method when the instance is taken out // of the pool of available instances to become associated with // a specific EJB object conn = getConnection(dsName); }
The container invokes the ejbRemove
method before removing the bean instance itself or by placing the instance back into the bean pool. This means that the information that was represented by this entity bean should be removed from within persistent storage. The employee example removes the employee and all associated information from the database before the instance is destroyed. Close the database connection.
public void ejbRemove() throws RemoveException { //Container invokes this method befor it removes the EJB object //that is currently associated with the instance ps = conn.prepareStatement("DELETE FROM EMPLOYEEBEAN WHERE EMPNO=?"); ps.setInt(1, this.empNo.intValue()); if (ps.executeUpdate() != 1) { throw new RemoveException("Failed to delete record"); } ps.close(); conn.close(); }
In addition to the configuration described in "Creating Entity Beans", you must modify and add the following to your ejb-jar.xml
deployment descriptor:
Configure the persistence type to be "Bean
" in the <persistence-type>
element.
Configure a resource reference for the database persistence storage in the <resource-ref>
element.
The employee example used the database environment element of "jdbc/OracleDS
". This is configured in the <resource-ref>
element as follows:
<resource-ref> <res-ref-name>jdbc/OracleDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Application</res-auth> </resource-ref>
The database specified in the <res-ref-name>
element maps to a <ejb-location>
element in the data-sources.xml
file. Our "jdbc/OracleDS
" database is configured in the data-sources.xml
file, as shown below:
<data-source
class="com.evermind.sql.DriverManagerDataSource"
name="Oracle"
location="jdbc/OracleCoreDS"
pooled-location="jdbc/pool/OraclePoolDS"
ejb-location="jdbc/OracleDS"
xa-location="jdbc/xa/OracleXADS"
connection-driver="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@myhost:1521:orcl"
username="scott"
password="tiger"
max-connections="300"
min-connections="5"
max-connect-attempts="10"
connection-retry-interval="1"
inactivity-timeout="30"
wait-timeout="30"
/>
Note: The entire BMP entity bean example is available on OTN from the OC4J sample code page athttp://www.oracle.com/technology/tech/java/oc4j/demos/ on the OTN Web site.
|
If your entity bean stores its persistent data within a database, you need to create the appropriate table with the proper columns for the entity bean. This table must be created before the bean is loaded into the database. The container will not create this table for BMP beans, but it will create it automatically for CMP beans.
In our employee example, you must create the following table in the database defined in the data-sources.xml
file:
Table | Columns |
---|---|
EMPLOYEEBEAN |
|
The following shows the SQL commands that create these fields.
CREATE TABLE EMPLOYEEBEAN ( EMPNO NUMBER NOT NULL, EMPNAME VARCHAR2(255) NOT NULL, SALARY FLOAT NOT NULL, CONSTRAINT EMPNO PRIMARY KEY )
Note: This book does not cover EJB container services. See the Data Source chapter in the Oracle Application Server Containers for J2EE Services Guide for information on how to configure your Data Source object. |