Oracle9i Enterprise JavaBeans Developer's Guide and Reference Release 1 (9.0.1) Part Number A90188-01 |
|
This chapter discusses what an entity bean is, how to create one, and how it is different from a session bean.
An entity bean is a remote object that manages persistent data, performs complex business logic, potentially uses several dependent Java objects, and can be uniquely identified by a primary key. Entity beans are normally coarse-grained persistent objects, in that they utilize persistent data stored within several fine-grained persistent Java objects.
An entity bean manages its data persistentcy through callback methods, which are defined in the javax.ejb.EntityBean
interface. When you implement the EntityBean
interface in your bean class, you develop each of the callback functions as designated by the type of persistence that you choose: bean-managed persistence or container-managed persistence. The container invokes the callback functions at designated times. That is, the contract between the container and the entity bean designates the order that the callback methods are invoked and who manages the bean's persistent data.
Each entity bean has a persistent identity associated with it. That is, the entity bean contains a unique identity that can be retrieved if you have the primary key. Given the primary key, a client can retrieve the entity bean. If the bean is not available, the container instantiates the bean and repopulates the persistent data for you.
The type for the unique key is defined by the bean provider.
When designing your EJB application, you need to keep in mind the aspects of each type of object.
Enterprise JavaBeans are remote objects and are used for interacting with clients over a network. Remote objects have a higher overhead for verifying security and transaction information. Thus, when you design your application, you may have an entity or session bean interacting with the client, but also have the Enterprise JavaBean invoke other dependent Java objects to perform tasks or manage persistent data.
Entity beans are normally used to manage complex, coarse-grained persistent data for a remote client. Be careful to separate the difference between an entity bean and a persistent object. Your entity bean should be more than just a persistent object; it should manage and return complex data to justify using a remote object for managing data.
You can have an entity bean that calls one or more dependent objects within the application. The entity bean is a remote object and thus its primary function is interacting with the client over the network. You should not have an entity bean invoking another entity bean within the same node on the network. If you need to design multiple objects within your application, design your application so that the entity bean facilitates the communication and data management between the client and other Java objects.
Figure 4-1 demonstrates how the client interacts with either a session or an entity bean, which then manages the application for the client with other Java objects within the application. The Java objects that make up the backend of the application can be persistent objects. The figure also shows how both the entity bean and a persistent Java object can be persistent and store data within the database.
For example, if you are managing a shopping cart for an online bookstore, you would have the following requirements:
In this scenario, the entity bean could do the following:
Thus, this entity bean not only retrieves persistent information from other objects, but would also maintain its own persistent data and perform complex calculations.
The major differences between session and entity beans is that entity beans involve a framework for persistent data management, a persistent identity, and complex business logic. The interface requirements on entity beans provides callback functions that the container calls when persistent data should be managed or when a bean should be retrieved based upon its identity.
With an entity bean, the interfaces have been designed so that each callback method is called at the appropriate time. For example, right before the transaction is committed, the ejbStore
method is always invoked. This enables the entity bean to save all of its persistent data before the transaction is completed. Each of these callback methods are discussed further in "Implementing Callback Methods".
The following table illustrates the different interfaces for session and entity beans. Notice that the difference between the two types of EJBs exists within the bean class and the primary key. All of the persistent data management is done within the bean class methods.
An entity bean is a remote object that manages its data persistently through callback methods, which are defined in the javax.ejb.EntityBean
interface. When you implement the EntityBean
interface in your bean class, you develop each of the callback functions as designated by the type of persistence that you choose: bean-managed persistence or container-managed persistence. The container invokes the callback functions at designated times, to manage the bean and its persistent data. That is, the contract between the container and the entity bean involves in what order the callback methods are invoked and who manages the bean's persistent data.
Your bean class implements the methods of the EntityBean
interface. The javax.ejb.EntityBean
interface has the following definition:
public interface javax.ejb.EntityBean extends javax.ejb.EnterpriseBean { public abstract void ejbActivate(); public abstract void ejbLoad(); public abstract void ejbPassivate(); public abstract void ejbRemove(); public abstract void ejbStore(); public abstract void setEntityContext(EntityContext ctx); public abstract voic unsetEntityContext(); }
The container expects these methods to have the following functionality:
An entity bean is similar to a session bean in that certain callback methods, such as ejbCreate
, are invoked at specified times. Entity beans use callback functions for managing its persistent data, primary key, and context information. The following diagram shows what methods are called when an entity bean is created.
This method is used by an entity bean instance to retain a reference to its context. Entity beans have contexts that the container maintains and makes available to the beans. The bean may use the methods in the entity context to retrieve information about the bean, such as security, and transactional role. Refer to the Enterprise JavaBeans 1.1 specification for the full range of information that you can retrieve about the bean from the context.
The container invokes setEntityContext
method, after it first instantiates the bean, to enable the bean to retrieve the context. The container will never call this method from within a transaction context. If the bean does not save the context at this point, the bean will never gain access to the context.
When the container calls this method, it passes the reference of the EntityContext
object to the bean. The bean can then store the reference for later use. The following example shows the bean saving the context in the this.ctx
variable.
public void setEntityContext(EntityContext ctx) { this.ctx = ctx; }
When the client invokes the remove
method, the container invokes the following methods.
In addition, the ejbStore
and ejbLoad
methods are called for managing your persistent data. These are the most important callback methods--for bean-managed persistent beans.
ejbStore
method is called by the container whenever a transaction is about to end. Its purpose is to save the persistent data to an outside resource, such as a database.
ejbLoad
method is called by the container whenever a transaction has begun or when an entity bean is instantiated. Its purpose is to restore any persistent data that exists for this particular bean instance.
The steps for creating an entity bean are the same as for a session bean. The difference is contained in the methods and data within the bean class. There are two types of entity beans: bean-managed persistent and container-managed persistent. This section discusses a bean-managed persistent bean. The "Container-Managed Persistence" gives an example of a container-managed persistent bean.
To create an entity bean, you perform the following steps:
javax.ejb.EJBObject
.
javax.ejb.EJBHome
. It defines the create
and finder methods, including findByPrimaryKey
, for your bean.
java.lang.String
, or be defined within its own class.
Similar to session beans, the entity bean's home interface must contain a create
method, which the client invokes to create the bean instance. Each create
method can have a different signature.
For an entity bean, you must develop a findByPrimaryKey
method. Because of the persistent data associated with the instance, each entity bean instance is uniquely identified by a primary key. The type for the unique key is defined by the developer. For example, the customer bean's primary key is the customer number. The purchase order's primary key is a purchase order number. The primary key can be anything--as long as it is unique.
When the entity bean is first created, the ejbCreate
method creates a primary key to identify the bean. A unique primary key is created and initialized within the ejbCreate
method in the bean class. From this time onward, this bean is associated with this primary key. Thus, you can retrieve the bean by providing the primary key object to the findByPrimaryKey
method.
Optionally, you can develop other finder methods to find the bean. These methods are named find<
name
>
.
To demonstrate an entity bean, we are creating a bean that manages a purchase order. The entity bean contains a list of items ordered by the customer.
The home interface extends javax.ejb.EJBHome
and defines the create
and findByPrimaryKey
methods.
package common; import java.rmi.RemoteException; import java.sql.SQLException; import javax.ejb.*; public interface PurchaseOrderHome extendsEJBHome
{ // Create a new PO public PurchaseOrderRemote create() throws CreateException, RemoteException; // Find an existing one public PurchaseOrderRemotefindByPrimaryKey
(String POnumber) throws FinderException, RemoteException; }
The entity bean remote interface is the interface that the customer sees and invokes methods upon. It extends javax.ejb.EJBObject
and defines the business logic methods. For our purchase order entity bean, the remote interface contains methods for adding items to the purchase order, for retrieving a list of all items within the purchase order, and computing the full price for the purchase order.
package common; import java.rmi.RemoteException; import java.sql.SQLException; import java.util.Vector; import javax.ejb.EJBObject; public interface PurchaseOrderRemote extendsEJBObject
{ // Price the PO public floatprice
() throws RemoteException; // Manage contents // getContents returns a Vector of LineItem objects public VectorgetContents
() throws RemoteException; public voidaddItem
(int sku, int count) throws RemoteException; }
Each entity bean instance has a primary key that uniquely identifies it from other instances. You can define your primary key in one of two ways:
java.lang.String
. If the primary key is a well-known data type, define the type in the <prim-key-class>
in the deployment descriptor.
name
>PK
class. If the primary key is a complex data type, define the primary key in a class that is serializable. This class is declared in the <prim-key-class>
element in the deployment descriptor.
Define your primary key to be a well-known type by defining the data type of the primary key within the deployment descriptor.
The purchase example defines its primary key as a java.lang.String
.
<enterprise-beans>
<entity>
<ejb-name>PurchaseOrderBean</ejb-name>
<home>common.PurchaseOrderHome</home>
<remote>common.PurchaseOrderRemote</remote>
<ejb-class>server.PurchaseOrderBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
</entity>
...
</enterprise-beans>
If your primary key is more complex than a simple data type, your primary key must be a class that is serializable of the name <
name
>PK
. Within this class, you should implement the equals
and hashCode
methods to provide for an implementation specific to this primary key.
The customer example declares its primary key--a customer identifier--within the PurchaseOrderPK.java
.
package common; public class PurchaseOrderPK implements java.io.Serializable { public int orderid; public boolean equals(Object obj) { if ((obj instanceof PurchaseOrderPK) && (((PurchaseOrderPK)obj).orderid == this.orderid)) return true; return false; } public int hashCode() { return orderid; } }
The class that defines the primary key is declared within the deployment descriptor, as follows:
<enterprise-beans>
<entity>
<ejb-name>PurchaseOrderBean</ejb-name>
<home>common.PurchaseOrderHome</home>
<remote>common.PurchaseOrderRemote</remote>
<ejb-class>server.PurchaseOrderBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>common.PurchaseOrderPK</prim-key-class>
<reentrant>False</reentrant>
</entity>
...
</enterprise-beans>
The ejbCreate
method is responsible primarily for the creation of the primary key. This involves creating the primary key, creating the persistent data representation for the key, initializing the key to a unique value, and returning this key to the invoker. The ejbFindByPrimaryKey
method is responsible for verifying that the primary key is still unique and returns it again to the container.
In the purchase order example, these methods perform the following:
ejbCreate
method initializes the primary key, ponumber
, to the next available number in the purchase order number sequence.
ejbFindByPrimaryKey
method is implemented to check that the purchase order number is valid and returns this number to the container.
// The create methods takes care of generating a new PO and returns // its primary key public StringejbCreate
() throws CreateException, RemoteException { String ponumber = null; try { //retrieve the next available purchase order number #sql { select ponumber.nextval into :ponumber from dual }; //assign this number as this instance's identification number #sql { insert into pos (ponumber, status) values (:ponumber, 'OPEN') }; } catch (SQLException e) { throw new PurchaseException (this, "create", e); } return ponumber; } // The ejbFindByPrimaryKey method verifies that the POnumber exists. This // method must return the primary key to the container.. which in turn // retrieves the instance based on the primary key. So.. this method must // only verify that the primary key is valid. public StringejbFindByPrimaryKey
(String ponumber) throws FinderException, RemoteException { try { int count; #sql { select count (ponumber) into :count from pos where ponumber = :ponumber }; // There has to be one if (count != 1) throw new FinderException ("Inexistent PO: " + ponumber); } catch (SQLException e) { throw new PurchaseException (this, "findByPrimaryKey", e); } // The ponumber is the primary key return ponumber; }
The entity bean class implements the following methods:
ejbCreate
method and any finder methods, including ejbFindByPrimaryKey
.
EntityBean
interface.
The following code implements methods of an entity bean called PurchaseOrderBean
.
The purchase order bean declares a vector to store all of the items within the customer's shopping cart. In addition, to retrieve environment information for the entity bean, an entity context is defined.
#sql iterator ItemsIter (int skunumber, int count, String description, float price); public classPurchaseOrderBean
implementsEntityBean
{ EntityContext ctx; Vector items; // The items in the PO (instances of LineItem)
The following is the implementation for the bean methods that were declared in the remote interface: price
, getContents
, and addItem
.
public floatprice
() throws RemoteException { float price = 0; Enumeration e = items.elements (); while (e.hasMoreElements ()) { LineItem item = (LineItem)e.nextElement (); price += item.quantity * item.price; } // 5% discount if buying more than 10 items if (items.size () > 10) price -= price * 0.05; // Shipping is a constant plus function of the number of items price += 10 + (items.size () * 2); return price; } // The getContents methods has to load the descriptions public VectorgetContents
() throws RemoteException { return items; } // The add Item method gets the price and description public voidaddItem
(int sku, int count) throws RemoteException { try { String description; float price; #sql { select price, description into :price, :description from skus where skunumber = :sku }; items.addElement (new LineItem (sku, count, description, price)); } catch (SQLException e) { throw new PurchaseException (this, "addItem", e); } }
Once you have implemented the business logic methods, you also must provide the following:
ejbCreate
method for each create
method defined in the home interface.
ejbFind<
name
>
method for each of the find<
name
>
methods defined in the home interface. This includes at least an ejbFindByPrimary
key method that returns the primary key to the container.
EntityBean
methods--These methods are callback methods that the container calls when necessary. Most of the callback methods pertain to managing the persistence of the entity bean's data.
The public constructor is called by the container to create the bean instance. The ejbCreate
and ejbPostCreate
methods are invoked to intialize this instance. The following is the purchase order constructor.
//provide an empty constructor for the creating the instance
public void PurchaseOrderBean
() {}
As shown in Figure 4-2, the ejbCreate
and ejbPostCreate
methods are invoked when the corresponding create
method--the methods all have the same arguments--is invoked. Typically, the ejbCreate
method initializes all of the persistent data; the ejbPostCreate
does any initialization that involves the entity's context. The context information is not available at ejbCreate
time, but is available at ejbPostCreate
time.
The following example shows the ejbCreate
and ejbPostCreate
for the purchase order example. The ejbCreate
method initializes the primary key, which is the purchase order number, and returns this key to the invoker. The purchase order line item vector is initialized within the ejbPostCreate
.
// The create methods takes care of generating a new PO and returns // its primary key public StringejbCreate
() throws CreateException, RemoteException { String ponumber = null; try { //retrieve the next available purchase order number #sql { select ponumber.nextval into :ponumber from dual }; //assign this number as this instance's identification number #sql { insert into pos (ponumber, status) values (:ponumber, 'OPEN') }; } catch (SQLException e) { throw new PurchaseException (this, "create", e); } return ponumber; } // create a vector to contain the purchase order line items. since this // is performed only once and needed for the lifetime of the object, it is // appropriate to create the vector in either ejbCreate or ejbPostCreate. public voidejbPostCreate
() { items = new Vector (); }
All entity beans must provide an ejbFindByPrimaryKey
method. You can also have other types of finder methods. Since the developer must implement any finder method declared within the home interface, there is no limitation on how many of these types of methods you can have. The only restrictions is that any finder method, other than the ejbFindByPrimaryKey
method, must return either a reference to the remote interface or an Enumeration
containing multiple references to remote interfaces. The ejbFindByPrimaryKey
method must return the primary key.
In order to provide other finder methods, you must do the following:
find<
name
>
in the home interface.
ejbFind<
name
>
in the bean class.
The following is the ejbFindByPrimaryKey
method for the purchase order example. It verifies that the primary key is valid. If so, it returns the key to the container. The container retrieves the correct bean instance for this key and returns the reference to the client.
// The findByPrimaryKey method verifies that the POnumber exists. This
// method must return the primary key to the container.. which in turn
// retrieves the instance based on the primary key. So.. this method must
// only verify that the primary key is valid.
public String ejbFindByPrimaryKey
(String ponumber)
throws FinderException, RemoteException
{
try {
int count;
#sql { select count (ponumber) into :count from pos
where ponumber = :ponumber };
// There has to be one
if (count != 1)
throw new FinderException ("Inexistent PO: " + ponumber);
} catch (SQLException e) {
throw new PurchaseException (this, "findByPrimaryKey", e);
}
// The ponumber is the primary key
return ponumber;
}
The main difference between entity and session beans is that entity beans possess persistent data that must be managed. When data is defined as persistent, it must be continually saved to or restored from a resource, such as a database or file. If the bean is destroyed, the persistent data can be restored without any loss.
The EntityBean
interface, which all entity beans implement, defines the following callback methods for managing the persistent data:
ejbStore
--saves the data to persistent storage
The container always invokes ejbStore
right before a transaction commits or the bean is removed to save the existing values of the persistent data.
ejbLoad
--loads the data saved within persistent storage into the bean
The container invokes ejbLoad
right after a bean is instantiated or a transaction begins.
Figure 4-4 shows how the persistent data within an entity bean can be saved to a database using ejbStore
. In addition, the data is restored from the database through ejbLoad
.
The following are the methods from the purchase order example. The ejbStore
method saves the purchase order items to the database. The ejbLoad
method restores the purchase order items from the database.
// The store method replaces all entries in the lineitems table with the // new entries from the bean public voidejbStore
() throws RemoteException { // Get the purchase order number String ponumber = (String)ctx.getPrimaryKey(); try { // Delete old entries in the database #sql { delete from lineitems where ponumber = :ponumber }; // Insert new entries from the vector in the bean. Crude, but effective. Enumeration e = items.elements (); while (e.hasMoreElements ()) { LineItem item = (LineItem)e.nextElement (); #sql { insert into lineitems (ponumber, skunumber, count) values (:ponumber, :(item.sku), :(item.quantity)) }; } } catch (SQLException e) { throw new PurchaseException (this, "store", e); } } // The load method populates the items array with all the saved // line items public voidejbLoad
() throws RemoteException { // Get the purchase order number String ponumber = (String)ctx.getPrimaryKey(); // Load all line items into a new vector. try { items = new Vector (); ItemsIter iter = null; try { #sql iter = { select lineitems.skunumber, lineitems.count, skus.description, skus.price from lineitems, skus where ponumber = :ponumber and lineitems.skunumber = skus.skunumber }; while (iter.next ()) { LineItem item = new LineItem (iter.skunumber(), iter.count(), iter.description(), iter.price()); items.addElement (item); } } finally { if (iter != null) iter.close (); } } catch (SQLException e) { throw new PurchaseException (this, "load", e); } }
The ejbRemove
method is invoked when the client invokes the remove method. For a bean-managed persistent bean, you must remove the data from the database that is associated with the bean within this method.
The following example shows how the purchase order line items are removed from the database.
// The remove method deletes all line items belonging to the purchase order
public void ejbRemove
() throws RemoteException {
// Get the purchase order number from the session context
String ponumber = (String)ctx.getPrimaryKey();
try {
//delete the line item vector for the purchase order
#sql { delete from lineitems where ponumber = :ponumber };
//delete the row associated with the purchase order
#sql { delete from pos where ponumber = :ponumber };
} catch (SQLException e) {
throw new PurchaseException (this, "remove", e);
}
}
If you want to access any information within the context during the lifetime of your application, you must save the context within the setEntityContext
.
public void setEntityContext(EntityContext ctx) { this.ctx = ctx; } public void unsetEntityContext() {}
Oracle does not currently support activation and passivation. However, you still must provide an empty implementation for these methods. ,
//There are no requirements for ejbActivate for this bean public voidejbActivate
() {} //There are no requirements for ejbPassivate for this bean public voidejbPassivate
() {}
The purchase order application persistently stores the individual orders within the purchase using a persistent Java object, called LineItem
. That is, the entity bean delegates management of each item in the purchase order to a non-EJB Java object.
package common; public class LineItem implements java.io.Serializable { public int sku; public int quantity; public String description; public float price; //Persistently manage each line item within the purchase order. public LineItem (int sku, int quantity, String description, float price) { //Each line item has the following information: SKU number, quantity, // description, and price. this.sku = sku; this.quantity = quantity; this.description = description; this.price = price; } }
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.
In our purchase order example, you must create the following tables:
The following shows the SQL commands that create these fields.
-- This sql scripts create the SQL tables used by the PurchaseOrder bean -- The sku table lists all the items available for purchase create table skus (skunumber number constraint pk_skus primary key, description varchar2(2000), price number); -- The pos table stores information about purchase orders -- The status column is 'OPEN', 'EXECUTED' or 'SHIPPED' create table pos (ponumber number constraint pk_pos primary key, status varchar2(30)); -- The ponumber sequence is used to generate PO ids create sequence ponumber; -- The lineitems table stores the contents of a po -- The skunumber is a reference into the skus table -- The ponumber is a reference into the pos table create table lineitems (ponumber number constraint fk_pos references pos, skunumber number constraint fk_skus references skus, count number); commit; exit;
You deploy the entity bean the same way as the session bean, which is detailed in "Deploying an EJB". In the same manner, you must create the XML deployment descriptors for the bean, create a JAR file containing all of the bean's files, and use deployejb
tool to load and publish the bean in the database.
The XML deployment descriptors are explained fully in Appendix A, "XML Deployment Descriptors". See the appendix for a full description on defining persistence for entity beans.
The following example demonstrates how the purchase order example deployment descriptors are organized. The features that this descriptor describes are as follows:
<trans-attribute>
element)
<default-enlist>
element)
<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.1 //EN" "ejb-jar.dtd"> <ejb-jar> <enterprise-beans> <entity> <description>no description</description> <ejb-name>PurchaseOrderBean</ejb-name> <home>common.PurchaseOrderHome</home> <remote>common.PurchaseOrderRemote</remote> <ejb-class>server.PurchaseOrderBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>java.lang.String</prim-key-class> <reentrant>False</reentrant> </entity> </enterprise-beans> <assembly-descriptor> <security-role> <description>no description</description> <role-name>PUBLIC</role-name> </security-role> <method-permission> <description>no description</description> <role-name>PUBLIC</role-name> <method> <ejb-name>PurchaseOrderBean</ejb-name> <method-name>*</method-name> </method> </method-permission> <container-transaction> <description>no description</description> <method> <ejb-name>PurchaseOrderBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
<?xml version="1.0"?> <!DOCTYPE oracle-descriptor PUBLIC "-//Oracle Corporation.//DTD Oracle 1.1//EN" "oracle-ejb-jar.dtd"> <oracle-descriptor> <mappings> <ejb-mapping> <ejb-name>PurchaseOrderBean</ejb-name> <jndi-name>test/PurchaseOrderBean</jndi-name> </ejb-mapping> <transaction-manager> <default-enlist>TRUE</default-enlist> </transaction-manager> </mappings> </oracle-descriptor>
To access a deployed entity bean, the client does one of the following:
When you access an entity bean, you must first locate the bean's home interface. You retrieve the home interface from the name space through JNDI. The URL must be of the following syntax:
<service_name>://<hostname>:<iiop_listener_port>:<SID>/<published_obj_name>
This syntax is described more in "Getting the Home Interface Object".
The following example retrieves the home interface of the EJB located published in /test/purchase
. The host, port, and SID are localhost, 2471, and ORCL respectively.
String serviceURL = "sess_iiop://localhost:2471:ORCL";
String objectName = "/test/purchase";
Hashtable env = new Hashtable(); env.put(Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put(Context.SECURITY_PRINCIPAL, user); env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN); Context ic = new InitialContext (env);CustomerHome ch = (CustomerHome)ic.lookup (serviceURL + objectName);
Customer myCust = (Customer) ch.create();
A client can access an existing entity bean through one of the following methods:
findByPrimaryKey
method, which returns the entity bean's remote reference.
Handle
object for the bean, invoke the getEJBObject
method on the Handle
object. This handle was created from the entity bean object using the getHandle
method. The handle can be passed, as is, to another object or it can be serialized and stored to be used at a later date.
There are two methods for managing the persistent data within an entity bean: bean-managed and container-managed persistence. The main difference between bean-managed and container-managed persistent beans is defined by who manages the persistence of the entity bean's data.
In practical terms, the following table provides a definition for both types and a summary of the programmatic and declarative differences between them:
Bean-Managed Persistence | Container-Managed Persistence | |
---|---|---|
Persistence management |
You are required to implement the persistence management within the
For example, the |
The management of the persistent data is done for you. That is, the container invokes a persistence manager on behalf of your bean.
You use |
Finder methods allowed |
The |
Only the |
Defining CMP fields |
N/A |
Required within the EJB deployment descriptor. The primary key must also be declared as a CMP field. |
Mapping CMP fields to resource destination. |
N/A |
Required. Dependent on persistence manager. |
Definition of persistence manager. |
N/A |
Required within the Oracle-specific deployment descriptor. See the next section for a description of a persistence manager. |
You can choose to have the container manage your persistent data for the bean. You have less to develop and manage, as the container stores and reloads your persistent data to and from the database.
When you use container-managed persistence, the container invokes a persistence manager class that provides the persistence management business logic. You can use either BC4J for your persistence management or the Oracle Persistence Service Interface Reference Implementation (PSI-RI). This document only discusses the PSI-RI implementation. See the BC4J documentation for information on their CMP manager.
To enable the container to manage your persistent data, you need to perform the following:
If you do not want to manage your persistent data, choose to have your bean managed by the container. This means that you do not have to implement some of the callback methods as the container and the persistence manager performs the persistence and primary key management for you. The container will still call these methods--so you can add logic for other purposes. You still must provide at least an empty implementation for all callback methods.
The following table details the implementation requirements for the bean class' callback functions:
PSI-RI enables you to perform a SQL query against the persistent data table through a CMP-only finder method with a find<
name>
naming syntax. This method takes a String that denotes the "where
" clause of a SQL query. Thus, the String would include the entire statement except for the "select
* from
<table"
. If you supply an empty string, all values are selected from this table.
You must define any such finder method within the Home interface. The container will provide the implementation for satisfying the where clause for the finder method.
For example, the following defines finder methods within the home interface, where one retrieves all customers and the other retrieves a single customer. Notice how the findAllCustomers
method, which retrieves all customers in the table, returns an Enumeration
.
public CustomerfindByWhere
(String whereString)
throws RemoteException, FinderException;
public java.util.EnumerationfindMultipleCustomers
(String whereString)
throws RemoteException, FinderException;
If you want to retrieve a single customer, provide the name, the SQL would be constructed as follows:
select * from customer where name = "Smith, John";
The findbyWhere
finder method would include the entire statement except for the "select * from customer
". It assumes that you want to select against the persistence table, as follows:
Customer find_customer = findByWhere ("where name = ", + custname);
If you want to select a few rows from employee based upon a certain condition, the SQL would be constructed as follows:
select * from customer where item_bought = treadmill order by name;
The find<
name>
method, which in this example is findByWhere
, would include the entire statement except for the "select
* from
employee
". It assumes that you want to select all matches against the persistence table. This is demonstrated below:
Enumeration customer_list = findMultipleCustomers(
"where item_bought = treadmill order by name");
Or, if you wanted a full customer listing, provide an empty string. The container will invoke a "select
* from
customer
" and retrieve all records.
Enumeration customer_list = findMultipleCustomers();
The main difference between defining a bean-managed and container-managed persistent primary key is that the fields within the key must be declared as container-managed persistent fields in the deployment descriptor. All fields within the primary key are restricted to be either primitive, serializable, and types that can be mapped to SQL types. See "Persistence Fields" for more information.
You can define your primary key in one of two ways:
Define your primary key as a container-managed persistent field and its type within the deployment descriptor. The following shows the primary key, custid
, declared as a <cmp-field>
and <primkey-field>
and its type declared within the <prim-key-class>
:
<enterprise-beans> <entity> <description>customer bean</description> <ejb-name>/test/customer</ejb-name> <home>customer.CustomerHome</home> <remote>customer.Customer</remote> <ejb-class>customerServer.CustomerBean</ejb-class><persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant><cmp-field><field-name>custid</field-name></cmp-field>
<cmp-field><field-name>name</field-name></cmp-field>
<cmp-field><field-name>addr</field-name></cmp-field>
<primkey-field>custid</primkey-field>
</entity> </enterprise-beans>
The primary key variable declared within the bean class must be declared as public
.
This should be a Java type that can be mapped to a SQL type through SQLJ. Also, this object must be serializable. See "Entity Bean Elements" for a full description.
If your primary key is more complex than a simple data type, you define the fields that make up the primary key as container-managed fields within the deployment descriptor. Then, you declare these fields as the primary key within a class. This class must be serializable. Also, all primary key variables declared within the bean class must be types that can be mapped to SQL types.
Within the bean class, the primary key variables must be declared as public
. Also, you must provide a constructor with no arguments for creating an empty primary key instance.
Within the serializable primary key class, you implement the equals
and hashCode
methods to provide for an implementation specific to this primary key.
The following example is a cruise ship cabin bean primary key that identifies each cabin with ship name, deck name, and cabin number.
package cruise; public class CabinPK implements java.io.Serializable { //Ship names { Castaway, LightFantastic, FantasyRide } public String ship; //Deck names { Upper Promendade, Promenade, Lower Promenade, Main Deck, Lower Deck } public String deck; //Cabin numbers A100-N300 public String cabin; //empty constructor public CabinPK ( ) { } public boolean equals(Object obj) { if ((obj instanceof CabinPK) && (((CabinPK)obj).ship == this.ship) && (((CabinPK)obj).deck == this.deck) && (((CabinPK)obj).cabin == this.cabin)) return true; return false; } public int hashCode() { return ((ship + deck + cabin).hash); }
The class that defines the primary key is declared within the XML deployment descriptor, as follows:
<enterprise-beans> <entity> <ejb-name>CabinBean</ejb-name> <home>cruise.CabinHome</home> <remote>cruise.Cabin</remote> <ejb-class>cruiseServer.CabinBean</ejb-class> <persistence-type>Container</persistence-type> <reentrant>False</reentrant><cmp-field><field-name>deck</field-name></cmp-field>
<cmp-field><field-name>cabin</field-name></cmp-field>
<prim-key-class>cruise.CabinPK</prim-key-class>
... </enterprise-beans>
See "Entity Bean Elements" for a full description of the deployment descriptor definition.
You must initialize all CMP fields, including the primary key, within the ejbCreate
method. The ejbCreate
method for the purchase order example initializes the primary key, ponumber
, to the next available number in the purchase order number sequence.
// The create methods takes care of generating a new PO and returns
// its primary key
public String ejbCreate
() throws CreateException, RemoteException
{
String ponumber = null;
try {
//retrieve the next available purchase order number
#sql { select ponumber.nextval into :ponumber from dual };
//assign this number as this instance's identification number
#sql { insert into pos (ponumber, status) values (:ponumber, 'OPEN') };
} catch (SQLException e) {
throw new PurchaseException (this, "create", e);
}
return ponumber;
}
All container-managed persistent fields must be declared as public
within your bean class. They cannot be transient. In addition, these fields are restricted to be either primitive, serializable, and types that can be mapped to SQL types. See Table A-2, "Unsupported Java Types for Persistent Variables" for more information.
The container invokes a persistence provider for managing your CMP bean. This release supports Oracle Persistence Service Interface Reference Implementation (PSI-RI). For more information on defining the provider, see "Defining Container-Managed Persistence" for a full description.
The following shows the portion of the Oracle-specific deployment descriptor that defines the persistence manager:
... <persistence-provider> <description> specifies a type of persistence manager </description> <persistence-name>psi-ri</persistence-name> <persistence-deployer>oracle.aurora.ejb.persistence.ocmp.OcmpEntityDeployer</p ersistence-deployer> </persistence-provider> <persistence-descriptor> <description> This specifies a particular type of persistence manager to be us ed for a bean. param is where you would put bean specific persistence info in t he format of params. The deployment process just passes what's in the param to the persistence deployer. For the baby persistence, we do parse the persistence -mapping but for other persistence backend we don't do anything with the params </description> <ejb-name>customerbean</ejb-name> <persistence-name>psi-ri</persistence-name> <psi-ri> <schema>SCOTT</schema> <table>customers</table> <attr-mapping> <field-name>custid</field-name> <column-name>cust_id</column-name> </attr-mapping> <attr-mapping> <field-name>name</field-name> <column-name>cust_name</column-name> </attr-mapping> <attr-mapping> <field-name>addr</field-name> <column-name>cust_addr</column-name> </attr-mapping> </psi-ri> </persistence-descriptor> </oracle-descriptor> </oracle-ejb-jar>
By default, the Container stores the persistence data into the database that is local to where the transaction was started--or continued with the Supports
attribute--by the Container. If you want to designate a certain database as the persistence storage database--as in an Oracle9i Application Server middle-tier--do the following:
DataSource
that designates the persistence storage database.
DataSource
within the <persistence-datasource>
element in the Oracle-specific deployment descriptor.
The following defines the PSI-RI persistence provider that uses a remote database to store the persistence fields. The "/test/empDatabase
" JTA DataSource
is defined in the following example as the remote persistence storage. This DataSource
was bound by bindds
with the -type jta
option.
<persistence-provider> <description> specifies a type of persistence manager </description> <persistence-name>psi-ri</persistence-name> <persistence-deployer>oracle.aurora.ejb.persistence.ocmp.OcmpEntityDeployer
</persistence-deployer><persistence-datasource>/test/empDatabase</persistence-datasource>
</persistence-provider>
If you are using Container-managed entity beans within your Oracle9i Application Server middle-tier, the Container defaults to storing the persistent data in the local database. However, the middle-tier contains a read-only cache; thus, you must specify the persistence storage database within the <persistence-database>
element.
All CMP data fields defined within your bean must be declared within the deployment descriptor in the <cmp-field>
element. See "Entity Bean Elements" for a full description. In addition, you must map these fields to the intended database table and respective columns. See "Persistence Fields" for more information.
The following is a portion of the Oracle-specific deployment descriptor, which maps the primary key and any container-managed persistence fields from the customer example (with the single primary key field) to the database table and column that the persistence provider stores the values for these fields within. Specifically, the container-manager stores the persistent fields into the customers table in SCOTT's schema. The persistent fields are mapped as follows:
Persistent Field | Table Column |
---|---|
custid (primary key) |
cust_id column in the customers table |
name |
cust_name column in the customers table |
addr |
cust_addr column in the customers table |
<psi-ri> <schema>SCOTT</schema> <table>customers</table> <attr-mapping> <field-name>custid</field-name> <column-name>cust_id</column-name> </attr-mapping> <attr-mapping> <field-name>name</field-name> <column-name>cust_name</column-name> </attr-mapping> <attr-mapping> <field-name>addr</field-name> <column-name>cust_addr</column-name> </attr-mapping> </psi-ri>
You can access EJB references and JDBC DataSources through your bean's environment.
The entity bean may create an environment reference for a target bean within the deployment descriptors. The URL of an EJB environment reference should have the following syntax:
"java:comp/env/ejb/"<ejb-ref-name
>
The "java:comp/env/ejb"
prefix is a subcontext that instructs JNDI to locate the EJB reference within the EJB references defined in the deployment descriptor. The <ejb-ref-name>
is the logical environment name of the EJB reference defined in the deployment descriptor. The following shows how a bean looks up another bean's reference within its deployed environment:
Context ic = new InitialContext ( );
CustomerHome ch = (CustomerHome)ic.lookup ("java:comp/env/ejb/PurchaseOrder");
See "Environment References To Other Enterprise JavaBeans" for a full description of EJB environment references.
The entity bean had the option of creating an environment reference for JDBC DataSource
objects bound within JNDI. These were declared for the bean within the deployment descriptors. If defined, the serviceURL
and objectName
contains the URL of an EJB environment reference, which is of the following syntax:
"java:comp/env/jdbc/"<resource-ref-name
>
The "java:comp/env/jdbc"
prefix is a subcontext that instructs JNDI to locate the JDBC DataSource within the <resource-ref>
elements defined in the deployment descriptor, which actually defines the logical environment name.
The following is the definition of the JDBC DataSource within the EJB deployment descriptor for the purchase order:
<resource-ref> <res-ref-name>jdbc/EmployeeAppDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Application</res-auth> </resource-ref>
The following shows the JNDI context initialization for the purchase order example:
#import javax.sql.*String dbURL = "java:comp/env/jdbc/EmployeeAppDB";
DataSource dbRes = (DataSource)ic.lookup (dbURL);
Connection conn = dbRes.getConnection;
See "Environment References To Resource Manager Connection Factory References" for a full description of JDBC DataSource
variables.
|
![]() Copyright © 1996-2001, Oracle Corporation. All Rights Reserved. |
|