The content of this guide is the following:
The description of these elements is provided in the following sections.
Note that in this documentation, the term "Bean" always means "Enterprise Bean."
An entity bean represents persistent data. It is an object view of an entity stored in a relational database. The persistence of an entity bean can be handled in two ways:
create methods:
Finder methods are used to search for an EJB object or a collection of EJB objects. The arguments of the method are used by the entity bean implementation to locate the requested entity objects. For bean-managed persistence, the bean provider is responsible for developing the corresponding ejbFinder methods in the bean implementation. For container-managed persistence, the bean provider does not write these methods; they are generated at deployment time by the platform tools; the description of the method is provided in the deployment descriptor, as defined in the section "Configuring database access for container-managed persistence." In the Home interface, the finder methods must adhere to the following rules:
home methods:
create table ACCOUNT (ACCNO integer primary key, CUSTOMER varchar(30), BALANCE number(15,4));
public interface AccountHome extends EJBHome {
public Account create(int accno, String
customer, double balance)
throws RemoteException,
CreateException;
public Account findByPrimaryKey(Integer
pk)
throws RemoteException,
FinderException;
public Account findByNumber(int accno)
throws RemoteException,
FinderException;
public Enumeration findLargeAccounts(double
val)
throws RemoteException,
FinderException;
}
The Component Interface is the client's view of an instance of the entity bean. It is what is returned to the client by the Home interface after creating or finding an entity bean instance. This interface contains the business methods of the enterprise bean. The interface must extend the javax.ejb.EJBObject interface if it is remote, or the javax.ejb.EJBLocalObject if it is local. The methods of a remote component interface must follow the rules for java RMI. For each method defined in this component interface, there must be a matching method of the bean implementation class (same arguments number and types, same return type, same exceptions except for RemoteException).
<prim-key-class>java.lang.Integer</prim-key-class>And, for container-managed persistence, the field which represents the primary key:
<primkey-field>accno</primkey-field>The alternative way is to define its own Primary Key class, described as follows:
The class must be serializable and must provide suitable implementation of the hashcode() and equals(Object) methods.
For container-managed persistence, the following rules must be followed:
public int accno;public AccountBeanPK(int accno) { this.accno = accno; }
public AccountBeanPK() { }
public int hashcode() { return accno; }
public boolean equals(Object other) {}...}
Special case: Automatic generation of Integer primary keys field
For a CMP 2.0 and CMP1 entity bean, the JOnAS-specific deployment
descriptor contains an additional, optional element,
automatic-pk, which may have one of the following values: true
or false (default value).
If the value is true, the primary key field's value of the bean is
automatically generated by the container when the entity bean is created. The
primary type java.lang.Integer must be selected for the primary key's field
in the <prim-key-class> tag, and the value of this field in the
ejbCreate method should not be initialized.
Example of using the new element:
JOnAS
specific deployment descriptor:
<jonas-entity>
<ejb-name>AddressEJB</ejb-name>
<jdbc-mapping>
<jndi-name>jdbc_1</jndi-name>
<automatic-pk>true</automatic-pk>
</jdbc-mapping>
</jonas-entity>
Standard deployment
descriptor:
<entity>
<ejb-name>AddressEJB</ejb-name>
<local-home>com.titan.address.AddressHomeLocal</local-home>
<local>com.titan.address.AddressLocal</local>
<ejb-class>com.titan.address.AddressBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>Cmp2_Address</abstract-schema-name>
<cmp-field><field-name>id</field-name></cmp-field>
<cmp-field><field-name>street</field-name></cmp-field>
<cmp-field><field-name>city</field-name></cmp-field>
<cmp-field><field-name>state</field-name></cmp-field>
<cmp-field><field-name>zip</field-name></cmp-field>
<primkey-field>id</primkey-field>
Address Bean
Class:
// Field Id is not initialized during ejbCreate
methode
public Integer ejbCreateAddress(String street, String city,
String state, String zip ) throws javax.ejb.CreateException {
setStreet(street);
setCity(city);
setState(state);
setZip(zip);
return null;
}
The first set of methods are those corresponding to the create and find methods of the Home interface:
This method is invoked by the container when a client invokes the corresponding create operation on the enterprise Bean's home interface. The method should initialize instance's variables from the input arguments. The returned object should be the primary key of the created instance. For bean-managed persistence, the bean provider should develop here the JDBC code to create the corresponding data in the database. For container-managed persistence, the container will perform the database insert after the ejbCreate method completes and the return value should be null.
There is a matching ejbPostCreate method (same input parameters) for each ejbCreate method. The container invokes this method after the execution of the matching ejbCreate(...) method. During the ejbPostCreate method, the object identity is available.
The container invokes this method on a bean instance that is not associated with any particular object identity (some kind of class method ...) when the client invokes the corresponding method on the Home interface. The implementation uses the arguments to locate the requested object(s) in the database and returns a primary key (or a collection thereof). Currently, collections will be represented as java.util.Enumeration objects or java.util.Collection. The mandatory FindByPrimaryKey method takes as argument a primary key type value and returns a primary key object (it verifies that the corresponding entity exists in the database). For container-managed persistence, the bean provider does not have to write these finder methods; they are generated at deployment time by the EJB platform tools. The information needed by the EJB platform for automatically generating these finder methods should be provided by the bean programmer. The EJB 1.1 specification does not specify the format of this finder method description; for JOnAS, the CMP 1.1 finder methods description should be provided in the JOnAS-specific deployment descriptor of the Entity Bean (as an SQL query). Refer to the section "Configuring database access for container-managed persistence." The EJB 2.0 specification defines a standard way to describe these finder methods, i.e. in the standard deployment descriptor, as an EJB-QL query. Also refer to the section "Configuring database access for container-managed persistence." Then, the methods of the javax.ejb.EntityBean interface must be implemented:
Used by the container to pass a reference to the EntityContext to the bean instance. The container invokes this method on an instance after the instance has been created. Generally, this method is used to store this reference in an instance variable.
Unset the associated entity context. The container calls this method before removing the instance. This is the last method the container invokes on the instance.
The container invokes this method when the instance is taken out of the pool of available instances to become associated with a specific EJB object. This method transitions the instance to the ready state.
The container invokes this method on an instance before the instance becomes dissociated with a specific EJB object. After this method completes, the container will place the instance into the pool of available instances.
This method is invoked by the container when a client invokes a remove operation on the enterprise bean. For entity beans with bean-managed persistence, this method should contain the JDBC code to remove the corresponding data in the database. For container-managed persistence, this method is called before the container removes the entity representation in the database.
The container invokes this method to instruct the instance to synchronize its state by loading it from the underlying database. For bean-managed persistence, the EJB provider should code at this location the JDBC statements for reading the data in the database. For container-managed persistence, loading the data from the database will be done automatically by the container just before ejbLoad is called, and the ejbLoad method should only contain some "after loading calculation statements."
The container invokes this method to instruct the instance to synchronize its state by storing it to the underlying database. For bean-managed persistence, the EJB provider should code at this location the JDBC statements for writing the data in the database. For entity beans with container-managed persistence, this method should only contain some "pre-store statements," since the container will extract the container-managed fields and write them to the database just after the ejbStore method call.
The following examples are for container-managed persistence with EJB 1.1 and EJB 2.0. For bean-managed persistence, refer to the examples delivered with your specific platform.
CMP 1.1
package eb; import java.rmi.RemoteException; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.ObjectNotFoundException; import javax.ejb.RemoveException; import javax.ejb.EJBException; public class AccountImplBean implements EntityBean { // Keep the reference on the EntityContext protected EntityContext entityContext; // Object state public Integer accno; public String customer; public double balance; public Integer ejbCreate(int val_accno, String val_customer, double val_balance) { // Init object state accno = new Integer(val_accno); customer = val_customer; balance = val_balance; return null; } public void ejbPostCreate(int val_accno, String val_customer, double val_balance) { // Nothing to be done for this simple example. } public void ejbActivate() { // Nothing to be done for this simple example. } public void ejbLoad() { // Nothing to be done for this simple example, in implicit persistence. } public void ejbPassivate() { // Nothing to be done for this simple example. } public void ejbRemove() { // Nothing to be done for this simple example, in implicit persistence. } public void ejbStore() { // Nothing to be done for this simple example, in implicit persistence. } public void setEntityContext(EntityContext ctx) { // Keep the entity context in object entityContext = ctx; } public void unsetEntityContext() { entityContext = null; } public double getBalance() { return balance; } public void setBalance(double d) { balance = balance + d; } public String getCustomer() { return customer; } public void setCustomer(String c) { customer = c; } public int getNumber() { return accno.intValue(); } }CMP 2.0
import java.rmi.RemoteException; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.ObjectNotFoundException; import javax.ejb.RemoveException; import javax.ejb.CreateException; import javax.ejb.EJBException; public abstract class AccountImpl2Bean implements EntityBean { // Keep the reference on the EntityContext protected EntityContext entityContext; /*========================= Abstract set and get accessors for cmp fields ==============*/ public abstract String getCustomer(); public abstract void setCustomer(String customer); public abstract double getBalance(); public abstract void setBalance(double balance); public abstract int getAccno(); public abstract void setAccno(int accno); /*========================= ejbCreate methods ============================*/ public Integer ejbCreate(int val_accno, String val_customer, double val_balance) throws CreateException { // Init object state setAccno(val_accno); setCustomer(val_customer); setBalance(val_balance); return null; } public void ejbPostCreate(int val_accno, String val_customer, double val_balance) { // Nothing to be done for this simple example. } /*====================== javax.ejb.EntityBean implementation =================*/ public void ejbActivate() { // Nothing to be done for this simple example. } public void ejbLoad() { // Nothing to be done for this simple example, in implicit persistence. } public void ejbPassivate() { // Nothing to be done for this simple example. } public void ejbRemove() throws RemoveException { // Nothing to be done for this simple example, in implicit persistence. } public void ejbStore() { // Nothing to be done for this simple example, in implicit persistence. } public void setEntityContext(EntityContext ctx) { // Keep the entity context in object entityContext = ctx; } public void unsetEntityContext() { entityContext = null; } /** * Business method to get the Account number */ public int getNumber() { return getAccno(); } }
public void doSomethingInDB (...) { conn = dataSource.getConnection(); ... // Database access operations conn.close(); }A DataSource object associates a JDBC driver with a database (as an ODBC datasource). It is created and registered in JNDI by the EJB server at launch time (refer also to the section "JDBC DataSources configuration").
A DataSource object is a resource manager connection factory for java.sql.Connection objects, which implements connections to a database management system. The enterprise bean code refers to resource factories using logical names called "Resource manager connection factory references." The resource manager connection factory references are special entries in the enterprise bean environment. The bean provider must use resource manager connection factory references to obtain the datasource object as follow:
<resource-ref> <res-ref-name>jdbc/AccountExplDs</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>The <res-auth> element indicates which of the two resource manager authentication approaches is used:
<jonas-entity> <ejb-name>AccountExpl</ejb-name> <jndi-name>AccountExplHome</jndi-name> <jonas-resource> <res-ref-name>jdbc/AccountExplDs</res-ref-name> <jndi-name>jdbc_1</jndi-name> </jonas-resource> </jonas-entity>The ejbStore method of the same Account example with bean-managed persistence is shown in the following example. It performs JDBC operations to update the database record representing the state of the entity bean instance. The JDBC connection is obtained from the datasource associated with the bean. This datasource has been instantiated by the EJB server and is available for the bean through its resource reference name, which is defined in the standard deployment descriptor.
In the bean, a reference to a datasource object of the EJB server is initialized:
it = new InitialContext();
ds = (DataSource)it.lookup("java:comp/env/jdbc/AccountExplDs");
Then, this datasource object is used in the implementation of the methods performing JDBC operations, such as ejbStore, as illustrated in the following:
public void ejbStore Connection conn = null; PreparedStatement stmt = null; try { // get a connection conn = ds.getConnection(); // store Object state in DB stmt = conn.prepareStatement("update account set customer=?,balance=? where accno=?"); stmt.setString(1, customer); stmt.setDouble(2, balance); Integer pk = (Integer)entityContext.getPrimaryKey(); stmt.setInt(3, pk.accno); stmt.executeUpdate(); } catch (SQLException e) { throw new javax.ejb.EJBException("Failed to store bean to database", e); } finally { try { if (stmt != null) stmt.close(); // close statement if (conn != null) conn.close(); // release connection } catch (Exception ignore) {} } }Note that the close statement instruction may be important if the server is intensively accessed by many clients performing entity bean access. If the statement is not closed in the finally block, since stmt is in the scope of the method, it will be deleted at the end of the method (and the close will be implicitly done). However, it may be some time before the Java garbage collector deletes the statement object. Therefore, if the number of clients performing entity bean access is important, the DBMS may raise a "too many opened cursors" exception (a JDBC statement corresponds to a DBMS cursor). Since connection pooling is performed by the platform, closing the connection will not result in a physical connection close, therefore opened cursors will not be closed. Thus, it is preferable to explicitly close the statement in the method.
It is a good programming practice to put the JDBC connection and JDBC statement close operations in a finally bloc of the try statement.
<persistence-type>container</persistence-type> <cmp-version>1.x</cmp-version> <cmp-field> <field-name>fieldOne</field-name> </cmp-field> <cmp-field> <field-name>fieldTwo</field-name> </cmp-field>With container-managed persistence the programmer need not develop the code for accessing the data in the relational database; this code is included in the container itself (generated by the platform tools). However, for the EJB platform to know how to access the database and which data to read and write in the database, two types of information must be provided with the bean:
For CMP 1.1, the bean deployer is responsible for defining the mapping of the bean fields to the database table columns. The name of the DataSource can be set at deployment time, since it depends on the EJB platform configuration. This database configuration information is defined in the JOnAS-specific deployment descriptor via the jdbc-mapping element. The following example defines the mapping for a CMP 1.1 entity bean:
<jdbc-mapping> <jndi-name>jdbc_1</jndi-name> <jdbc-table-name>accountsample</jdbc-table-name> <cmp-field-jdbc-mapping> <field-name>mAccno</field-name> <jdbc-field-name>accno</jdbc-field-name> </cmp-field-jdbc-mapping> <cmp-field-jdbc-mapping> <field-name>mCustomer</field-name> <jdbc-field-name>customer</jdbc-field-name> </cmp-field-jdbc-mapping> <cmp-field-jdbc-mapping> <field-name>mBalance</field-name> <jdbc-field-name>balance</jdbc-field-name> </jdbc-mapping>jdbc_1 is the JNDI name of the DataSource object identifying the database. accountsample is the name of the table used to store the bean instances in the database. mAccno, mCustomer, and mBalance are the names of the container-managed fields of the bean to be stored in the accno, customer, and balance columns of the accountsample table. This example applies to container-managed persistence. For bean-managed persistence, the database mapping does not exist.
For a CMP 2.0 entity bean, only the jndi-name element of the jdbc-mapping is mandatory, since the mapping may be generated automatically (for an explicit mapping definition, refer to the "JOnAS Database Mapping" section of the Using CMP2.0 persistence chapter):
<jdbc-mapping> <jndi-name>jdbc_1</jndi-name> </jdbc-mapping> <cleanup>create</cleanup>For a CMP 2.0 entity bean, the JOnAS-specific deployment descriptor contains an additional element, cleanup, at the same level as the jdbc-mapping element, which can have one of the following values:
For each finder method, this element provides a way to define an SQL WHERE clause that will be used in the generated finder method implementation to query the relational table storing the bean entities. Note that the table column names should be used, not the bean field names. Example:
<finder-method-jdbc-mapping> <jonas-method> <method-name>findLargeAccounts</method-name> </jonas-method> <jdbc-where-clause>where balance > ?</jdbc-where-clause> </finder-method-jdbc-mapping>The previous finder method description will cause the platform tools to generate an implementation of ejbFindLargeAccount(double arg) that returns the primary keys of the entity bean objects corresponding to the tuples returned by the "select ... from Account where balance > ?", where '?' will be replaced by the value of the first argument of the findLargeAccount method. If several '?' characters appear in the provided WHERE clause, this means that the finder method has several arguments and the '?' characters will correspond to these arguments, adhering to the order of the method signature.
In the WHERE clause, the parameters can be followed by a number, which
specifies the method parameter number that will be used by the query in this
position.
Example: The WHERE clause of the following finder method can be:
Enumeration findByTextAndDateCondition(String text, java.sql.Date date) WHERE (description like ?1 OR summary like ?1) AND (?2 > date)Note that a <finder-method-jdbc-mapping> element for the findByPrimaryKey method is not necessary, since the meaning of this method is known.
Additionally, note that for CMP 2.0, the information defining the behaviour of the implementation of a find<method> method is located in the standard deployment descriptor, as an EJB-QL query (i.e. this is not JOnAS-specific information). The same finder method example in CMP 2.0:
<query> <query-method> <method-name>findLargeAccounts</method-name> <method-params> <method-param>double</method-param> </method-params> </query-method> <ejb-ql>SELECT OBJECT(o) FROM accountsample o WHERE o.balance > ?1</ejb-ql> </query>The datatypes supported for container-managed fields in CMP 1.1 are the following:
Java Type | JDBC Type | JDBC driver Access methods |
boolean | BIT | getBoolean(), setBoolean() |
byte | TINYINT | getByte(), setByte() |
short | SMALLINT | getShort(), setShort() |
int | INTEGER | getInt(), setInt() |
long | BIGINT | getLong(), setLong() |
float | FLOAT | getFloat(), setFloat() |
double | DOUBLE | getDouble(), setDouble |
byte[] | VARBINARY or LONGVARBINARY (1) | getBytes(), setBytes() |
java.lang.String | VARCHAR or LONGVARCHAR (1) | getString(), setString() |
java.lang.Boolean | BIT | getBoolean(), setObject() |
java.lang.Integer | INTEGER | getInt(), setObject() |
java.lang.Short | SMALLINT | getShort(), setObject() |
java.lang.Long | BIGINT | getLong(), setObject() |
java.lang.Float | REAL | getFloat(), setObject() |
java.lang.Double | DOUBLE | getDouble(), setObject() |
java.math.BigDecimal | NUMERIC | getBigDecimal(), setObject() |
java.math.BigInteger | NUMERIC | getBigDecimal(), setObject() |
java.sql.Date | DATE | getDate(), setDate() |
java.sql.Time | TIME | getTime(), setTime() |
java.sql.Timestamp | TIMESTAMP | getTimestamp(), setTimestamp() |
any serializable class | VARBINARY or LONGVARBINARY (1) | getBytes(), setBytes() |
(1) The mapping for String will normally be VARCHAR, but will turn into LONGVARCHAR if the given value exceeds the driver's limit on VARCHAR values. The case is similar for byte[] and VARBINARY and LONGVARBINARY values.
For CMP 2.0, the supported datatypes depend on the JORM mapper used.