The target audience for this guide is the Enterprise Bean provider, i.e. the person in charge of developing the software components on the server side (the Enterprise Beans).
The contents of this guide are the following:
An Enterprise Bean is composed of the following parts, that are to be developed by the Enterprise Bean Provider:
Note: in this documentation, the term "Bean" always means "Enterprise Bean".
All the exceptions defined in the throws clause of an ejbCreate method must be defined in the throws clause of the matching create method of the home interface.
public interface OpHome extends EJBHome { Op create(String user) throws CreateException, RemoteException; }
Used by the container to pass a reference to the SessionContext to the bean instance. The container invokes this method on an instance after the instance has been created. Generally this method stores this reference in an instance variable.
public void ejbRemove();
This method is invoked by the container when the instance is in the process of being removed by the container. Since most session Beans don't have any resource state to clean up, the implementation of this method is typically left empty.
public void ejbPassivate();
This method is invoked by the container when it wants to passivate the instance. After this method completes, the instance must be in a state that allows the container to use the Java Serialization protocol to externalize and store away the instance's state.
public void ejbActivate();
This method is invoked by the container when the instance has just been reactivated. The instance should acquire any resource that it has released earlier in the ejbPassivate() method.
A session Bean can optionally implement the javax.ejb.SessionSynchronization interface. This interface can provide the Bean with transaction synchronization notification.The Session Synchronization interface methods that the EJB provider must develop are the following:
This method notifies a session Bean instance that a new transaction has started. At this point the instance is already in the transaction and may do any work it requires within the scope of the transaction.
public void afterCompletion(boolean committed);
This method notifies a session Bean instance that a transaction commit protocol has completed, and tells the instance whether the transaction has been committed or rolled back.
public void beforeCompletion();
This method notifies a session Bean instance that a transaction is about to be committed.
public class OpBean implements SessionBean,
SessionSynchronization {
protected int total = 0;// actual state of the
bean
protected int newtotal = 0;// value inside Tx, not yet
committed.
protected String clientUser = null;
protected SessionContext sessionContext =
null;
public void ejbCreate(String user) throws
RemoteException {
total = 0;
clientUser = user;
}
public void ejbActivate() throws
RemoteException {
// Nothing to do for this simple example
}
public void ejbPassivate() throws
RemoteException {
// Nothing to do for this simple example
}
public void ejbRemove() throws
RemoteException {
// Nothing to do for this simple example
}
public void setSessionContext(SessionContext
sessionContext) throws RemoteException {
this.sessionContext =
sessionContext;
}
public void afterBegin() throws
RemoteException {
newtotal = total;
}
public void beforeCompletion() throws
RemoteException {
// Nothing to do for this simple example
}
public void buy(int s) {
newtotal = newtotal + s;
return;
}
public int read() {
return newtotal;
}
}
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. In case of bean-managed
persistence, the bean provider is responsible for developing the corresponding
ejbFind
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(AccountBeanPK
pk)
throws RemoteException,
FinderException;
public Account findByNumber(int accno)
throws RemoteException,
FinderException;
public Enumeration findLargeAccounts(double
val)
throws RemoteException,
FinderException;
}
The Remote 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. The methods of this interface must follow the rules for java RMI. For each method defined in this remote interface, there must be a matching method of the bean implementation class (same arguments number and types, same return type, same exceptions).
For container-managed persistence, the following rules must be followed:
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 bean provider should develop here the JDBC code to create the corresponding data in the database. The returned object should be the primary key of the created instance.
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 bean provider does not have to develop the database access code, the container will perform the database insert after the ejbCreate method completes. The return type must be void.
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 to 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. The mandatory FindByPrimaryKey method takes as argument a primary key type value and returns a primary key object (it checks that the corresponding entity exists in the database).
In case of 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 specification does not specify the format of this finder method description. For JOnAS, the finder methods description should be provided in the deployment descriptor of the Entity Bean, see section "Configuring database access for container-managed persistence".
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.
public void unSetEntityContext();
Unset the associated entity context. The container calls this method before removing the instance. This is the last method that the container invokes on the instance.
public void ejbActivate();
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. Currently not used by the JOnAS platform.
public void ejbPassivate();
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. Currently not used by the JOnAS platform.
public void ejbRemove();
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. In case of container-managed persistence, this method is called before the container removes the entity representation in the database.
public void ejbLoad();
The container invokes this method to instruct the instance to synchronize its state by loading it from the underlying database. In case of bean-managed persistence, the EJB provider should code at this place 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".
public void ejbStore();
The container invokes this method to instruct the instance to synchronize its state by storing it to the underlying database. In case of bean-managed persistence, the EJB provider should code at this place 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.
package eb; import java.rmi.RemoteException; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.ObjectNotFoundException; import javax.ejb.RemoveException; public class AccountImplBean implements EntityBean { // Keep the reference on the EntityContext protected EntityContext entityContext; // Object state public int accno; public String customer; public double balance; public void ejbCreate(int val_accno, String val_customer, double val_balance) throws RemoteException { // Init object state accno = val_accno; customer = val_customer; balance = val_balance; } public void ejbPostCreate(int val_accno, String val_customer, double val_balance) throws RemoteException { // Nothing to be done for this simple example. } public void ejbActivate() throws RemoteException { // Nothing to be done for this simple example. } public void ejbLoad() throws RemoteException { // Nothing to be done for this simple example, in implicit persistence. } public void ejbPassivate() throws RemoteException { // Nothing to be done for this simple example. } public void ejbRemove() throws RemoteException, RemoveException { // Nothing to be done for this simple example, in implicit persistence. } public void ejbStore() throws RemoteException { // Nothing to be done for this simple example, in implicit persistence. } public void setEntityContext(EntityContext ctx) throws RemoteException { // Keep the entity context in object entityContext = ctx; } public void unsetEntityContext() { entityContext = null; } public double getBalance() throws RemoteException { return balance; } public void setBalance(double d) throws RemoteException { balance = balance + d; } public String getCustomer() throws RemoteException { return customer; } public void setCustomer(String c) throws RemoteException { customer = c; } public int getNumber() throws RemoteException { return accno; } }
A method that performs database access should always contain the getConnection and close statements, as follows:
public void doSomethingInDB (...) { conn = dataSource.getConnection(); ... // Database access operations conn.close(); }
A DataSource object associates a JDBC driver and a database (as an ODBC datasource); it is generally registered in JNDI by the EJB server at launch time (see also the section about JDBC DataSources configuration).
Somewhere in the bean, a reference to a datasource object of the EJB server is initialized. In the entity bean example of the platform, it is done in the setEntityContext method.
Properties env = ctx.getEnvironment(); // ctx is the EntityContext String dataSourceName = env.getProperty("datasource.name"); dataSource = (DataSource)initialContext.lookup(dataSourceName);
Then, this datasource object is used in the implementation of the methods performing JDBC operations, such as ejbStore, as illustrated below:
public void ejbStore() throws RemoteException {
try { // get a connection
conn =
dataSource.getConnection();
// store Object state in
DB
PreparedStatement stmt =
conn.prepareStatement("update account set customer=?,balance=? where
accno=?");
stmt.setString(1,
customer);
stmt.setDouble(2,
balance);
AccountBeanPK pk =
(AccountBeanPK) entityContext.getPrimaryKey();
stmt.setInt(3, pk.accno);
stmt.executeUpdate();
// close statement
stmt.close();
// release connection
conn.close();
} catch (SQLException e) {
throw new
java.rmi.RemoteException("Failed to store bean to database", e);
}
}
Note that the close statement instruction may be important if the server is intensively accessed by many clients performing entity beans access. 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 take 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 "two 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, and therefore opened cursors will not be closed. So it is preferable to explicitly close the statement in the method.
It could be a good programming rule to put the JDBC connection and JDBC statement close operations in a finally bloc of the try statement.
First of all, the standard way to indicate to an EJB platform that an entity bean has container-managed persistence is to fill the "ContainerManagedFields" attribute of the deployment descriptor with the list of container-managed fields (the fields that the container will have in charge to make persistent). In the textual format of the deployment descriptor, this is represented by the following line:
ContainerManagedFields = {fieldOne; fieldTwo; fieldFour; };
With container-managed persistence, the programmer does not have to 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, in order that the EJB platform knows how to access the database and which data to read and write in the database, two kinds of information must be provided with the bean :
The EJB specification does not specify how this information should be provided to the EJB platform by the bean provider and the bean deployer. Therefore, what is described in the remainder of this section is specific to JOnAS.
The bean provider is responsible for defining the mapping of the bean fields to the database table columns. The name of the DataSource may be set at deployment time, as it depends on the EJB platform configuration. This database configuration information is defined in the environment properties of the bean deployment descriptor. These properties are classified in two categories:
EnvironmentProperties = "file_name";
where the file file_name contains the bean environment properties. This file is a standard java.util.Properties file. The properties file should be as below, it defines the DataSource object to be used and the mapping of the bean fields onto the columns of a relational table:
datasource.name
db.TableName
db.Field.
This is illustrated in the example below:
datasource.name jdbc_1
db.TableName Account
db.Field.mAccno accno
db.Field.mCustomer customer
db.Field.mBalance balance
jdbc_1 is the JNDI name of the DataSource object identifying the database, Account is the name of the table used to store the bean instances in the database, and mAccno, mCustomer, mBalance are the names of the container-managed fields of the bean to be stored in the accno, customer and balance columns of the Account table. This example applies for container-managed persistence, in case of bean-managed persistence, the database mapping does not exist, and the DataSource name property should be part of the bean properties file.
The container properties of an entity bean with container-managed
persistence may also contain information defining the behaviour of the
implementation of a find
db.Finder.find
For each finder method, this property defines a 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:
db.Finder.findLargeAccount where balance > ?
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 they will correspond to these arguments, respecting the order of the method signature.
Note that if you intend to write a "where clause" spanning several lines in the property file, end of line characters should be preceded by a '\' character. Example:
db.Finder.findRangeAccount where balance > ? \ and balance < ?
The datatypes supported for container-managed fields 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 | getObject(), setObject() |
java.lang.Integer | INTEGER | getObject(), setObject() |
java.lang.Long | BIGINT | getObject(), setObject() |
java.lang.Float | REAL | getObject(), setObject() |
java.lang.Double | DOUBLE | getObject(), setObject() |
java.math.BigDecimal | NUMERIC | getObject(), 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.
The transactional behaviour of an enterprise bean is defined at configuration time, and is part of the deployment descriptor of the bean. It is possible to define a common behaviour for all the methods of the bean, or to define it at the method level. It is achieved by specifying a transactional attribute, which can be one of the following:
Transaction Attribute | Client transaction | Transaction associated with enterprise Bean's method |
TX_NOT_SUPPORTED | -
T1 |
-
- |
TX_REQUIRED | -
T1 |
T2
T1 |
TX_REQUIRES_NEW | -
T1 |
T2
T2 |
TX_MANDATORY | -
T1 |
error
T1 |
TX_SUPPORTS | -
T1 |
-
T1 |
In the deployment descriptor, the specification of the transactional attributes appears in the ControlDescriptors part as follows:
ControlDescriptors = {
{ TransactionAttribute = TX_SUPPORTS; };
{ Method = getBalance;
TransactionAttribute = TX_REQUIRED; };
{ Method = setBalance;
TransactionAttribute = TX_MANDATORY;
};
}
In this example, for all methods not explicitly specified in the ControlDescriptors, the default transactional attribute is TX_SUPPORTS (defined at the bean-level descriptor attribute), and for the methods getBalance and setBalance their respective transactional attributes are TX_REQUIRED and TX_MANDATORY (defined at the method-level descriptor attributes).
The TX_BEAN_MANAGED transaction attribute must not be mixed with the other values of the transaction attributes. This means that if the bean-level descriptor attribute or one of the method-level descriptor attributes specifies the TX_BEAN_MANAGED attribute, then all method-level descriptor attributes must specify TX_BEAN_MANAGED. The usual ControlDescriptor for a bean which manages the transaction demarcation by itself is
ControlDescriptors = {
{ TransactionAttribute = TX_BEAN_MANAGED; };
}
To demarcate the transaction boundaries in a TX_BEAN_MANAGED method, the bean programmer should use the javax.transaction.UserTransaction interface, defined on an EJB server object that may be obtained using the EJBContext.getUserTransaction() method (the SessionContext object or the EntityContext object depending if the method is defined on a session or an entity bean). The example below shows a session bean method "doTxJob" demarcating the transaction boundaries; the UserTransaction object is obtained from the sessionContext object, that should have been initialized in the setSessionContext method (see the example of the session bean).
public void doTxJob() throws RemoteException { UserTransaction ut = sessionContext.getUserTransaction(); ut.begin(); ... // transactional operations ut.commit(); }
As explained in the previous section, the transactional behaviour of an application can be defined in a declarative way, or coded in the bean and/or the client itself (transaction boundaries demarcation). In any case, the distribution aspects of the transactions are completely transparent to the bean provider and to the application assembler. This means that a transaction may involve beans located on several EJB servers and that the platform will take in charge the management of the global transaction by itself. It will perform the two phase commit protocol between the different servers and the bean programmer will have nothing to do for this purpose.
Once the beans have been developed and the application has been assembled, it is possible for the deployer and for the administrator to configure the distribution of the different beans on one or several machines, and within one or several EJB servers. This may be done without impacting neither the beans code nor their deployment descriptors. The distributed configuration is specified at launch time: in the environment properties of an EJB server, you may specify
To achieve this goal, two properties must be set in your jonas.properties file, jonas.beans.descriptors and jonas.tm.remote. The first one lists the beans that will be handled on this EJB server (by specifying the name of their deployment descriptors), and the second one sets the Java Transaction Monitor (JTM) launching mode:
Example:
jonas.beans.descriptors Bean1.ser, Bean2.ser jonas.tm.remote false
The Java Transaction Monitor may run outside of any EJB server, in this case, it may be launched in a stand alone way using the following command:
TMServer
Using this configuration facilities, it is possible to adapt the beans distribution to the resources (cpu and data) location, so that performance may be optimal.
The figure below illustrates four cases of distribution configuration of three beans.
These different configuration cases may be obtained by launching the EJB servers and eventually the JTM (case 3) with the adequate properties. The rational when opting for one of these configurations is resources location and load balancing. However, the following hints may be noted:
The only information that indicates at deployment time to the EJB platform that an Entity Bean has a container-managed persistence is the presence of the "ContainerManagedFields" attribute in the deployment descriptor.
In the case of JOnAS (i.e. this is not in the EJB specification), the deployment descriptor may be represented in a textual format; it may then be translated into a serialized java object by the GenDD tool.
Example of Session Descriptor:
SessionDescriptor {
BeanHomeName = "ExHome";
EnterpriseBeanClassName = tests.ExBean;
HomeInterfaceClassName = tests.ExHome;
RemoteInterfaceClassName = tests.Ex;
ControlDescriptors = {
{ TransactionAttribute =
TX_NOT_SUPPORTED; };
{ Method = methodOne;
TransactionAttribute = TX_REQUIRED; };
{ Method = methodTwo(int);
TransactionAttribute = TX_MANDATORY; };
{ Method =
methodTwo(java.lang.String);
TransactionAttribute = TX_MANDATORY; };
};
EnvironmentProperties = "BeanEnv.properties";
StateManagementType = STATELESS_SESSION;
}
Example of Entity Descriptor:
EntityDescriptor {
BeanHomeName = "ExHome";
EnterpriseBeanClassName = tests.ExBean;
HomeInterfaceClassName = tests.ExHome;
RemoteInterfaceClassName = tests.Ex;
PrimaryKeyClassName = tests.PK;
ControlDescriptors = {
{ TransactionAttribute =
TX_NOT_SUPPORTED; };
{ Method = methodOne;
TransactionAttribute = TX_REQUIRED; };
{ Method = methodTwo(int);
TransactionAttribute = TX_MANDATORY; };
{ Method =
methodTwo(java.lang.String);
TransactionAttribute = TX_MANDATORY; };
};
EnvironmentProperties = "BeanEnv.properties";
ContainerManagedFields = {
fieldOne; fieldTwo;
fieldFour;
};
}
In order to test the enterprise bean, the bean developer should develop a client application (see chapter Application Assembler's Guide), and follow the following steps:
javac Account.java AccountBeanPK.java ClientAccount.java AccountBean.java AccountHome.java
GenDD Account.txt
GenIC Account.ser
Enterprise Beans are packaged for deployment in a standard Java programming language Archive file, called an ejb-jar file. This file must contain
Name:
Enterprise-Bean: True
To build the ejb-jar file of the Account entity bean example, the java source files should have been compiled to obtain the class files and the GenDD tool should have been used to produce the serialized deployment descriptor Op.ser (see the previous section).
A file called "manifest" must be built, containing the following lines:
Name: Op.ser
Enterprise-Bean: True
Then, the ejb-jar file (OpEB.jar) is built, using the jar command:
cd your_bean_class_directory
cp .../Account.ser .
jar cvmf manifest OpEB.jar sb/*.class Op.ser
For both container-managed or bean-managed persistence, JOnAS makes use of relational storage systems through the JDBC interface. JDBC connections are obtained from an object provided at the EJB server level, the DataSource. The DataSource interface is defined in the JDBC 2.0 standard extensions. A DataSource object identifies a database and a mean to access it via JDBC (a JDBC driver). An EJB server may propose access to several databases and thus provide the corresponding DataSource objects. One may add DataSource objects available on the platform; they are defined in the BullEJB.properties file. This section explains how DataSource objects may be defined and configured in the EJB server, although this is more an administrative task than a bean developer task.
Implementations of the DataSource interface should be provided by the JDBC driver vendors. As such classes are not currently available, JOnAS provides such an implementation of the DataSource interface that allows you to define DataSource objects for two relational database management server products, Oracle and InstantDB (a free, 100% java, Relational Database Management System).
The way to define DataSource objects in order to make them available to an EJB platform is not specified in the EJB specification. Therefore, the remainder of this section, which describes how to define and configure DataSource objects, is specific to JOnAS. However, the way to use these DataSource objects in the Enterprise Bean methods is standard (example in section "Writing database access operations").
A DataSource object should be defined in a file called
In the BullEJB.properties file, to define a DataSource "Oracle1", you should add its name "Oracle1" (name of the properties file) to the line jonas.datasources, as follows:
jonas.datasources Oracle1,InstantDB1
The property file defining a DataSource should contain the following information:
datasource.name | JNDI name of the DataSource |
datasource.url | The JDBC database URL : jdbc: |
datasource.classname | Name of the class implementing the JDBC driver |
datasource.username | Database user name |
datasource.password | Database user password |
A DataSource object for Oracle (say Oracle1), named "jdbc_1" in JNDI, and using the Oracle "thin" JDBC driver, should be described in a file called Oracle1.properties, as in the example below:
datasource.name jdbc_1 datasource.url jdbc:oracle:thin:@malte:1521:ORA1 datasource.classname oracle.jdbc.driver.OracleDriver datasource.username scott datasource.password tiger
In this example, "malte" is the hostname of the server running the Oracle DBMS, 1521 is the SQL*Net V2 port number on this server, and ORA1 is the ORACLE_SID.
This example makes use of the Oracle "Thin" JDBC driver. If your EJB server is running on the same host as the Oracle DBMS, you may use the Oracle OCI JDBC driver; in this case the URL to be used is jdbc:oracle:oci7: or jdbc:oracle:oci8:, depending of your Oracle release. Oracle JDBC drivers may be downloaded at their Web site.
If you intend to create an InstantDB DataSource object (say InstantDB1), named "jdbc_2" in JNDI, it should be described as follows (in a file InstantDB1.properties):
datasource.name jdbc_2 datasource.url jdbc:idb=Account.prp datasource.classname jdbc.idbDriver datasource.username unuseful datasource.password unuseful
Properties having the "unuseful" value are not used for this kind of persistence storage.
For the database user and password, you may choose to put it in the
DataSource description (
This section lists the different aspects in the Enterprise Bean development cycle which are specific to JOnAS (since they are not defined in the EJB specification).