Enterprise JavaBeans (EJB) Guide

A guide to the old Enterprise JavaBeans (EJB) framework.

Posted on September 17, 2004 by Ernesto Garbarino

Overview

Enterprise JavaBeans (EJB) is a server-side component architecture for the development and deployment of distributed object systems for the Java platform. Applications written using the EJB architecture are scalable, transactional, and multi-user secure.

EJB is not a technology that you can test right away like Swing. You have to consider that there is more than one player in the game; an EJB container (usually supplied by 3rd party vendor), a client and the client-jar generated by the 3rd party deployment tool. For this reason, in order to try out my examples it will be also necessary to use Caucho’s Resin 3.0. Although EJB should be portable, there are subtle incompatibilities that make difficult to write truly cross-container examples (including table-mapping information).

Bean types

Entity Bean

Session Bean

Message-driven Bean

Session Beans

A session bean represents a single client inside the J2EE server. To access an application that is deployed on the server, the client invokes the session bean’s methods. The session bean performs work for its client, shielding the client from complexity by executing business tasks inside the server. As its name suggests, a session bean is similar to an interactive session. A session bean is not shared–it may have just one client, in the same way that an interactive session may have just one user. Like an interactive session, a session bean is not persistent. (That is, its data is not saved to a database.) When the client terminates, its session bean appears to terminate and is no longer associated with the client.

A “Hello World” Framework

The most basic EJB component requires three files and an XML deployment descriptor. The example is about a “Greeter” component which has only one duty; to return a “Hello World” string to its clients. Components are assembled into a ejb-jar file that includes a META-INF directory in which the XML deployment descriptor is included.

GreeterEjb.jar
  |
  |-org.garba.sessionbeans/
  |          |
  |          |-Greeter.class
  |          |-GreeterBean.class
  |          |-GreeterHome.class
  |
  |-META-INF/
       |
       |-ejb-jar.xml
    

The Component Interface

The business methods implemented by the Greeter bean go in this interface. The interface extends EJBObject. The Greeter bean must not implement this interface.

When you think about a business component you may want to start by defining the methods it has. The component interface is the place where you define all the methods of your “own”. Then, you implement these methods in the bean class but the bean class DOES NOT INCLUDE the component interface. The container does that because it has to add services in-between.

Greeter.java

 01 package org.garba.sessionbeans;
 02 
 03 import java.rmi.RemoteException;
 04 import javax.ejb.EJBObject;
 05 
 06 public interface Greeter extends EJBObject {
 07 
 08 public String getGreeting() throws RemoteException;
 09 
 10 } 

Component interface rules:

  • It must import java.rmi.RemoteException
  • It must import java.ejb.*
  • It must extend EJBObject.
  • It must declare one or more business methods that throw a RemoteException.
  • Arguments and return types must be legal RMI-IIOP types (primitive, Serializable, Remote or arrays/collections of these types).
  • Overloaded methods are allowed.
  • Each method must declare a RemoteException.
  • You can declare your own application exceptions as long as they are checked exception (i.e. subclasses of Exception, not of RuntimeException).
  • Business methods names must not begin with the string “ejb”.

The Bean Class

The real business logic goes here. The bean class always implements a Session, Enity or MessageDriven interface.

GreeterBean.java

 01 package org.garba.sessionbeans;
 02 
 03 import javax.ejb.SessionBean;
 04 import javax.ejb.SessionContext;
 05 
 06 public class GreeterBean implements SessionBean {
 07 
 08 public void ejbActivate() {
 09 System.out.println("ejb activate");
 10 }
 11 
 12 public void ejbPassivate() {
 13 System.out.println("ejb passivate");
 14 }
 15 
 16 public void ejbRemove() {
 17 System.out.println("ejb remove");
 18 }
 19 
 20 public void setSessionContext(SessionContext ctx) {
 21 System.out.println("session context");
 22 }
 23 
 24 public String getGreeting() {
 25 return "Hello World";
 26 }
 27 
 28 /* It does not come from the SessionBean interface */
 29 
 30 public void ejbCreate() {
 31 System.out.print("ejb create");
 32 }
 33 
 34 }

Rules for the session bean class

  • The following methods from the SesssionBean interface must be implemented:
    • void ejbActivate()
    • void ejbPassivate()
    • void ejbRemove()
    • void setSessionContext(SessionContext ctx)
  • Business methods must be declared as public, and must not be declared as final or static.
  • Method names must not begin with the string “ejb”.
  • You must not pass “this” as an argument or return value because the bean itself must never be exposed.
  • Arguments and return types for Remote component interface methods must be legal RMI-IIOP types (primitive, Serializable, Remote or arrays/collections of these types)
  • You don’t have to declare the exceptions declared in the component interface, unless you might actually throw those excpetions from your own methods.
  • You must never declare a RemoteException
  • You must not declare any application exceptions (checked exceptions) that were not declared in the component interface for that matching business method.
  • The bean class is always Serializable even if you don’t see “implements Serializable”
  • The class must be public and cannot be final or abstract.
  • The constructor must be public and without arguments. It is always recommended to let the compiler insert the default constructor given that anyways there’s nothing to be put inside the constructor.

The Home Interface

This interface is used to ask for a reference to the component interface.

GreeterHome.java

 01 package org.garba.sessionbeans;
 02 
 03 import java.rmi.RemoteException;
 04 import javax.ejb.CreateException;
 05 import javax.ejb.EJBHome;
 06 
 07 public interface GreeterHome extends EJBHome {
 08 
 09 public Greeter create() throws CreateException, RemoteException;
 10 
 11 }

Home interface rules:

  • It must import java.rmi.RemoteException
  • It must import javax.ejb.*;
  • Declares a create() method that returns the component interface (The object that extendes EJBObject) and inexorably declares a CreateException and RemoteException. (The bean developer may add other checked exceptions)
  • FOR STATELESS SESSION BEANS (this example):
    • There can only be one create() without arguments.
  • FOR STATEFUL SESSION BEANS:
    • There can be multiple create() methods.
    • A no arguments create() is not mandatory.
    • The name of a create methods must begin with “create”. Example: createUser().
    • Arguments must be legal RMI-IIOP types (primitive, Serializable, Remote or arrays/collection containing this type of objects)

The ejb-jar.xml file

Describes the structure of the EJB component; how the three files (Greeter.java, GreeterBean.java, GreeterHome.java) are related.

ejb-jar.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 
 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
 <ejb-jar>
    <display-name>Ejb1</display-name>
    <enterprise-beans>
       <session>
          <display-name>GreeterBean</display-name>
          <ejb-name>GreeterBean</ejb-name>
          <home>org.garba.sessionbeans.GreeterHome</home>
          <remote>org.garba.sessionbeans.Greeter</remote>
          <ejb-class>org.garba.sessionbeans.GreeterBean</ejb-class>
          <session-type>Stateful</session-type>
          <transaction-type>Bean</transaction-type>
          <security-identity>
             <description></description>
             <use-caller-identity></use-caller-identity>
          </security-identity>
       </session>
    </enterprise-beans>
 </ejb-jar>

The Client

The client could be located in a remote machine. The way to reach the JNDI service the first time depends of the EJB vendor. In this example we assume that the Greeter component has been deployed on a local J2EE SDK 1.3 server

GreeterClient.java

01 import org.garba.sessionbeans.Greeter;
02 import org.garba.sessionbeans.GreeterHome;
03 
04 import javax.naming.Context;
05 import javax.naming.InitialContext;
06 import javax.rmi.PortableRemoteObject;
07 
08 public class GreeterClient {
09 
10 public static void main(String[] args) {
11 try {
12 Context ic = new InitialContext();
13 Object o = ic.lookup("greeter");
14 GreeterHome home = (GreeterHome) PortableRemoteObject.narrow(o, GreeterHome.class);
15 Greeter greeter = home.create();
16 System.out.println(greeter.getGreeting());
17 } catch (Exception ex) {
18 ex.printStackTrace();
19 }
20 }
21 
22 }

Result:

Hello World `

Line 12: To do a JNDI lookup, the client must first get an InitialContext which is entry point of the directory hierarchy.
Line 13: The client does a lookup on JNDI, using the logical name under which the bean was deployed.
Line 14: For a Remote home interface, the stub returned from JNDI must be both cast and narrowed.
Line 15: The create() method always returns the component interface and thus a cast is no needed.

EJBHome Methods Seen by The Client

Along with the create() methods that you supply in the home interface, you also get the following EJBHome methods:

EJBMetaData getEJBMetadata()

Returns the EJBMetaData interface that provides further class information about the bean.

GreeterHome home = (GreeterHome) PortableRemoteObject.narrow(o, GreeterHome.class);
EJBMetaData metaData = home.getEJBMetaData();
System.out.println("Is session: " + metaData.isSession());
System.out.println("Is stateless session: " + metaData.isStatelessSession());
System.out.println("Home interface class: " + metaData.getHomeInterfaceClass());
// Session beans do not have a primary key
if (!metaData.isSession()) {
System.out.println("Primary key class: " + metaData.getPrimaryKeyClass());
}
System.out.println("Remote interface class: " + metaData.getRemoteInterfaceClass());
 

Result:

Is session: true
Is stateless session: false
Home interface class: interface org.garba.sessionbeans.GreeterHome
Remote interface class: interface org.garba.sessionbeans.Greeter

HomeHandle getHomeHandle()

Return a Serializable object that allows to retrive the home at a later time without needing to go through the JNDI lookup.

 GreeterHome home = (GreeterHome) PortableRemoteObject.narrow(o, GreeterHome.class);
 
 // Prepare a file called saved.dat
 
 FileOutputStream fos = new FileOutputStream("saved.dat");
 ObjectOutputStream dst = new ObjectOutputStream(fos);
 
 // Write handle to disk
 
 HomeHandle homeHandle = home.getHomeHandle();
 dst.writeObject(homeHandle);
 
 dst.close();
 fos.close();
 
 // Read handle from disk
 
 FileInputStream fis = new FileInputStream("saved.dat");
 ObjectInputStream src = new ObjectInputStream(fis);
 
 HomeHandle homeHandleFromDisk = (HomeHandle)src.readObject();
 
 // Get a new working GreeterHome and confirm success
 
 GreeterHome newHome = (GreeterHome) PortableRemoteObject.narrow(homeHandleFromDisk.getEJBHome(), GreeterHome.class);
 Greeter greeter = newHome.create();
 System.out.println(greeter.getGreeting());
 Result:
 
 Hello World

Once you run this program for the first time, you can comment all the JNDI calls and start straight off from the reading part. If you switch off the application server the handle does not usually get back a working home interface and it is necessary to repeat the whole JNDI lookup process again. Don’t forget to use PortableRemoteObject.narrow instead of just a plain cast.

void remove(Handle h)

Tells the home that you no longer need a session bean.

Greeter greeter = home.create();
System.out.println(greeter.getGreeting());
home.remove(greeter.getHandle());
System.out.println(greeter.getGreeting()); // Warning! ERROR

Result:

Hello World
java.rmi.NoSuchObjectException

The error originates after the second access to the component method. Please note that the greeter object doesn’t become null. The stub still exists but has lost its connection with the server.

void remove(Object key)

Deletes the entity from the persistent store. This method cannot be used for session beans. Session and entity beans have a common interface with different remove methods that must be chosen according to the bean type.

EJBObject Methods Seen by The Client

Along with the business methods that you supply in the component interface, you also get the following EJBObject methods:

Object getPrimaryKey()

Obtains the primary key of an entity bean. It doesn’t apply to session beans (produces an exception).

EJBHome getEJBHome()

Returns a reference to the bean’s home. It allows to create more beans if you have lost the home (for example if you have passed the bean to a method) without incurring in the JNDI lookup process.

public static void main(String[] args) {
try {
Context ic = new InitialContext();
Object o = ic.lookup("greeter"); 
GreeterHome home = (GreeterHome) PortableRemoteObject.narrow(o, GreeterHome.class);
Greeter greeter = home.create();
sillyMethod(greeter);
} catch (Exception ex) {
ex.printStackTrace();
}
}

public static void sillyMethod(Greeter greeter) {
try {
GreeterHome rospoHome = (GreeterHome)greeter.getEJBHome();
Greeter greeter1 = rospoHome.create();
Greeter greeter2 = rospoHome.create();
Greeter greeter3 = rospoHome.create();
System.out.println(greeter1.getGreeting());
System.out.println(greeter2.getGreeting());
System.out.println(greeter3.getGreeting());
} catch (RemoteException e) {
e.printStackTrace();
} catch (CreateException e) {
e.printStackTrace();
} 
}

Result:

Hello World
Hello World
Hello World

Handle getHandle()

Gets a reference to the EJBObject that may be stored to get back to the bean at a later time.

GreeterHome home = (GreeterHome) PortableRemoteObject.narrow(o, GreeterHome.class); Greeter greeter = home.create();

saveToDisk(greeter.getHandle());
Handle handle = (Handle)loadFromDisk();

Greeter savedGreeter = (Greeter) PortableRemoteObject.narrow(handle.getEJBObject(), Greeter.class);
System.out.println(savedGreeter.getGreeting());

If you want to see how saveToDisk()/loadFromDisk() may be implemented check the EJBHome.getHomeHandle() example.
Never forget to use PortableRemoteObject.narrow() to get the right object.

void remove()

If it is a session bean, the bean becomes no longer active and the container saves resources. If it is an entity bean, the container not only makes the bean inactive, it also erases the information represented by the bean from the persistent store.

 Greeter greeter = home.create();
 System.out.println(greeter.getGreeting());
 greeter.remove();
 System.out.println(greeter.getGreeting()); // Warning! ERROR

 Result:
 
 Hello World
 java.rmi.NoSuchObjectException

The error originates after the second access to the component method. Please note that the greeter object doesn’t become null. The stub still exists but it has lost its connection with the server.

boolean isIdentical(Object o)

Checks whether two EJB objects are equal as seen by the container. The equals() method is inappropriate because it compares two objects on the same HEAP. i.e. the same JVM.

Some rules apply:

  • For stateless session beans: isIdentical() returns true if both references come from the same home, even if the stubs are referring to two different Remote EJB objects.
  • For stateful session beans: isIdentical() returns false for any two unique stubs in all cases.
  • For entity beans: isIdenticial() returns true if the stubs refer to two entities with the same primary key.

The EJBLocalHome Interface

We have previously seen the Remote interfaces that allow to access a component either from another JVM instance or from a distant computer (maybe even in another country). The local interfaces, EJBLocalHome for EJBHome and EJBLocalObject for EJBObject allow access to the components when the EJB container and the client share the same virtual machine. The local interfaces are simpler and have less methods because many methods only make sense when there’s a stub and a “remote” server.

Methods Seen by The Client

EJBLocalHome EJBHome
EJBMetaData EJBMetaData()
HomeHandle getHomeHandle()
void remove(Handle h)
void remove(Object key) void remove(Object key)

Example

01 package org.garba.sessionbeans;
02 
03 import javax.ejb.CreateException;
04 import javax.ejb.EJBLocalHome;
05 
06 public interface GreeterLocalHome extends EJBLocalHome {
07 
08 public GreeterLocal create() throws CreateException;
09 
10 }

As you can see, the create() method at line 8 doesn’t declare a RemoteException as GreeterHome.java.

Rules for The EJBLocalHome Interface

The EJBLocalObject Interface

The EJBLocalObject interface has only one method less than the EJBObject interface but the method for getting the home only returns an object of EJBLocalHome type.

Methods Seen by The Client

EJBLocalObject EJBObject
Object getPrimaryKey() Object getPrimaryKey()
EJBLocalHome getEJBLocalHome() EJBHome getEJBHome()
Handle getHandle()
void remove() void remove()
boolean isIdentical() boolean isIdentical()

Example

1 package org.garba.sessionbeans;
2 
3 import javax.ejb.*;
4 
5 public interface GreeterLocal extends EJBLocalObject {
6 
7 public String getGreeting();
8 
9 }

As you can see, the getGreeting() method at line 7 doesn’t declare a RemoteException as Greeter.java.

Rules for The EJBLocalObject Interface

A Local Client

01 import org.garba.sessionbeans.GreeterLocal;
02 import org.garba.sessionbeans.GreeterLocalHome;
03 
04 import javax.ejb.CreateException;
05 import javax.naming.Context;
06 import javax.naming.InitialContext;
07 import javax.naming.NamingException;
08 
09 public class GreeterClient {
10 
11 public static void main(String[] args) {
12 
13 Context ic = null;
14 GreeterLocalHome home = null;
15 GreeterLocal greeterLocal = null;
16 
17 try {
18 
19 ic = new InitialContext();
20 Object o = ic.lookup("greeterLocal");
21 
22 // There is no need for PortableRemoteObject.narrow()
23 
24 home = (GreeterLocalHome) o; 
25 greeterLocal = home.create();
26 
27 } catch (NamingException e) {
28 e.printStackTrace();
29 } catch (CreateException e) {
30 e.printStackTrace();
31 }
32 
33 // This method doesn't throw a RemoteException
34 
35 System.out.println(greeterLocal.getGreeting());
36 
37 }
38 }

Note the differences between a local client and a client for remote components:

Key Moments in a Session Bean’s Life

rospo

Creation

Stateless session beans can have only one create() in the home interface and thus only one ejbCreate() counterpart. Stateful session beans instead, must have at least one create() method (not precisely without arguments) but can have many more. A method such as createUser(String user) in the home interface will require an ejbCreateUser(String user) method in the bean class. A certified container won’t deploy an EJB component that doesn’t follow this pattern.

Creation events in order

  1. Object construction
  2. setSessionContext()
  3. ejbCreate()

Passivation

A passivated bean is temporarily saved in some kind of secondary storage, to conserve resources between client calls. If a pasivated bean times out, the bean will simply die, without first being reactivated. A bean that is in a transaction cannot be pasivated

Passivation events in order

  1. ejbPassivate()
  2. ejbActivate()

Removal

Session bean removal takes place when the client explicitley calls the remove() method or when the container decides that the bean has timed out. Under normal
circumstances the ejbRemove() method will be called before the bean dies although some unexpected error might prevent this from happening.

Passivation

When the container calls the ejbPassivate() method of a stateful bean, it is necessary to do the necessary work to leave all variables in a serialization-compatible state. Transient variables are not expected to return to their initial (or null) state when serialized, they must be properly set in ejbActivate(). A stateful session bean will never be passivated while the bean is still in a transaction.

When ejbPassivate() completes, every non-transient variable must be a reference to one of the following:

Example of good behavior

Connection connection = null;

public void ejbCreate() {
try {
connection = DBPool.getConnection();
} catch (Exception ex) {
// Error
}
}

public void ejbPassivate() throws EJBException, RemoteException {
connection = null;
 }

public void ejbActivate() throws EJBException, RemoteException {
try {
connection = DBPool.getConnection();
} catch (Exception ex) {
// Error
}

Removal

A session bean dies for one of these three reasons:

1. The client calls remove()

The formal way of removing a bean is calling remove() in the home or the component interface. In every case the container will call ejbRemove() on the bean so there is a chance to perform a bit of cleanup.

2. The bean times out

If it is a stateful bean in active state, ejbRemove() will be called as expected, instead if the container finds the bean in passivated mode, the container WILL NOT CALL ejbRemove(). In this case the bean will be sent straight to the Garbage Collector.

3. The bean throws a system exception (or the container crashes)

The container WILL NOT CALL ejbRemove() under any circumstance.

The SessionContext Interface

The container calls setSessionContext() only once in the bean’s life, at the beginning of its life. The SessionContext interface (that extends EJBContext) provides the only methods that allow the bean to talk to the container and get information about the client in the case of stateful session beans

EJBHome getEJBHome()

EJBLocalHome getEJBLocalHome()

Get a reference to the home for remote and local components respectively.

EJBObject getEJBObject()

EJBLocalObject getEJBLocalObject()

Get a reference to bean’s EJB object for remote and local components respectively.

Principal getCallerPrincipal()

boolean isCallerInRole(String s)

get security information about the client

void setRollbackOnly(boolean b)

force a transaction to rollback (CMT only)

boolean getRollbackOnly()

find out if the transaction has already been set to rollback (CMT only)

UserTransaction getUserTransaction()

get a transaction reference, and start your own transaction (BMT only)

Entity Beans

An entity bean represents a business object in a persistent storage mechanism. Typically, each entity bean has an underlying table in a relational database, and each instance of the bean corresponds to a row in that table. Entity beans are persistent, allow shared access, have primary keys, and may participate in relationships with other entity beans. Persistence may be implemented by the bean developer (BMP) Bean-Managed Persistence or by the Container (CMP) Container-Manager Persistence

A “Customer” framework

The most basic entity includes the same amount of elements that the session “hello world” example. In this case we use CMP in order to make things easier.

CustomerEjb.jar
  |
  |-org.garba.entitybeans/
  |          |
  |          |-Customer.class
  |          |-CustomerBean.class
  |          |-CustomerHome.class
  |
  |-META-INF/
       |
       |-ejb-jar.xml
    

The Component Interface

An entity bean Remote component interface extends EJBObject. There’s no separate interface for session beans and entity beans. This interface usually contains getters and setters for field values that correspond to columns in the database table as seen in the example.

Customer.java

01 package org.garba.entitybeans;
02 
03 import javax.ejb.EJBObject;
04 import java.rmi.RemoteException;
05 
06 public interface Customer extends EJBObject {
07 
08 public Integer getCustomerId() throws RemoteException;
09 public String getName() throws RemoteException;
10 public void setName(String name) throws RemoteException;
11 
12 }

Component Interface Rules:

  • It must import java.rmi.RemoteException
  • It must import javax.ejb.*
  • It must extend EJBObject.
  • Arguments and return types must be legal RMI-IIOP types (primitive, Serializable, Remote or arrays/collections of these types).
  • Overloaded methods are allowed.
  • Each method must declare a RemoteException.
  • You can declare your own application exceptions as long as they are checked exceptions (i.e. subclasses of Exception, not of RuntimeException).
  • Methods names must not begin with the string “ejb”.

The Bean Class

The EntityBean interface has three additional methods not present in the SessionBean interface that the bean developer must implement:

  • public void unsetEntityContext()
  • public void ejbLoad()
  • public void ejbStore()
  • void setEntityContext(EntityContext ec) (not exactly new but different)

CustomerBean.java

01 package org.garba.entitybeans;
02 
03 import javax.ejb.EntityBean;
04 import javax.ejb.EntityContext;
05 
06 public abstract class CustomerBean implements EntityBean {
07 
08 private static int counter = 0;
09 private int beanNumber;
10 
11 private EntityContext context;
12 
13 public CustomerBean () {
14 counter++;
15 beanNumber = counter;
16 System.out.println("Constructor (" + beanNumber + ")");
17 }
18 
19 public abstract Integer getCustomerId();
20 public abstract String getName();
21 public abstract void setName(String name);
22 
23 public void setEntityContext(EntityContext context) {
24 System.out.println("setEntityContext (" + beanNumber + ")");
25 this.context = context;
26 }
27 
28 public Integer ejbCreate(String name) {
29 System.out.println("ejbCreate (" + beanNumber + ")");
30 this.setName(name);
31 return null;
32 }
33 
34 public void ejbPostCreate(String name) {
35 System.out.println("ejbPostCreate (" + beanNumber + ")");
36 }
37 
38 public void unsetEntityContext() {
39 System.out.println("unsetEntityContext (" + beanNumber + ")");
40 }
41     
42 public void ejbLoad() {
43 System.out.println("ejbLoad (" + beanNumber + ")");
44 }
45 
46 public void ejbStore() {
47 System.out.println("ejbStore (" + beanNumber + ")");
48 }
49 
50 public void ejbRemove() {
51 System.out.println("ejbRemove (" + beanNumber + ")");
52 }
53 
54 public void ejbActivate() {
55 System.out.println("ejbActivate (" + beanNumber + ")");
56 }
57 
58 public void ejbPassivate() {
59 System.out.println("ejbPassivate (" + beanNumber + ")");
60 }
61 
62 }
EntityBean interface
setEntityContext(EntityContext ec)
Container gives the bean reference to its context.

unsetEntityContext()
Called when the Container wants to reduce the size of the pool.

ejbPassivate()
Called when the bean is about to return to the pool, following a transaction.

ejbActivate()
Called when the bean is taken out of the pool to service a client’s business method call.

ejbRemove()
Called when the client calls remove(), and wants to delete this entity from the database.

ejbLoad()
Called when the bean has been refreshed with data from the underlying persistent store.

ejbLoad()
Called when the Container is about to update the database to reflect the state of the bean.

Bean class rules:

  • The following methods from the EntityBean interface must be implemented:
    • public void ejbActivate()
    • public void ejbPassivate()
    • public void ejbRemove()
    • public void setEntityContext(EntityContext ctx) (different)
    • public void unsetEntityContext() (new)
    • public void ejbLoad() (new)
    • public void ejbStore() (new)
  • In this case we use CMP so getters and setters must be abstract (thus the class must be also abstract). If the bean programmer must implement validation he can choose not to expose the setter/getter in the component interface and expose instead a method that contains the validation code:

    public abstract setRealName(String realname); public void setName(String name) { if (name == null) name = "Unnamed"; this.setRealName(name); }

  • If you have create() methods in your home, you must match each create() with two methods: ejbCreate() and ejbPostCreate().
  • Business methods must be declared as public, and must not be declared as final or static.
  • Method names must not begin with the string “ejb”.
  • You must not pass “this” as an argument or return value because the bean itself must never be exposed.
  • Arguments and return types for Remote component interface methods must be legal RMI-IIOP types (primitive, Serializable, Remote or arrays/collections of these types)
  • You don’t have to declare the exceptions declared in the component interface, unless you might actually throw those excpetions from your own methods.
  • You must never declare a RemoteException
  • You must not declare any application exceptions (checked exceptions) that were not declared in the component interface for that matching business method.
  • The bean class is always Serializable even if you don’t see “implements Serializable”
  • The class must be public and cannot be final or abstract.
  • The constructor must be public and without arguments. It is always recommended to let the compiler insert the default constructor given that anyways there’s nothing to be put inside the constructor.

The Home Interface

An entity bean home interface is substantially different from that of a session bean, because entity beans are typically found rather than created. In an entity home, a create() method is not required. Entity beans home interfaces can have single-row or multi-row finder methods. Both create and finder methods return the component interface of a bean, although multi-entity finders return a Collection of component interface references. Every entity bean home is required to have at least one method -the findByPrimaryKey() method that searches for a particular entity and returns its component interface or throws an exception. Multiple-entity finders do not throw an exception if no matching entities are found. They simply return an empty collection.

The entity home interface can also have home businesses methods, for operations that apply to more than one entity, as opposed to one specific entity. The real benefit of home business methods is that -unlike create and finder methods- can return something other than an EJB object reference. If the clients wants only data, for example, a Collection of Strings representing the name and phone number of each customer, a home business method can do that while a finder can not. CustomerHome.java

01 package org.garba.entitybeans;
02 
03 import java.rmi.RemoteException;
04 
05 import javax.ejb.CreateException;
06 import javax.ejb.EJBHome;
07 import javax.ejb.FinderException;
08 
09 public interface CustomerHome extends EJBHome {
10 
11 public Customer findByPrimaryKey(Integer key) throws FinderException, RemoteException;
12 public Customer create(String name) throws CreateException, RemoteException;
13 
14 }

Home Interface Rules:

  • It must import java.rmi.RemoteException
  • It must import javax.ejb.*
  • It must extend EJBHome.
  • Declare (optionally) one or more create() methods which must return the Remote component interface, and declare both a RemoteException and a CreateException. Each create() method must begin with the prefix “create”.
  • Declare the findByPrimaryKey() method, which must return a remote component interface, and declare both a RemoteException and a FinderException.
  • Declare one or more home business methods.
    • Arguments and return types must be RMI-IIOP compatible (Serializable, primitive, Remote, or array/collections of any of those).
    • Overloaded methods are allowed.
    • Each method must declare a RemoteException.
    • The user may declare his own exceptions, but they must not be runtime exceptions, i.e. subclasses of Exception and not subclasses of RuntimeException.
    • Methods can have arbitrary names, as long as they don’t begin with “create”, “find”, or “remove”.

The ejb-jar.xml file

The XML descriptor is a bit different from the session bean’s example.

<?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?>
 <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 
 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
 <ejb-jar>
    <display-name>Ejb1</display-name>
    <enterprise-beans>
       <session>
          <display-name>GreeterBean</display-name>
          <ejb-name>GreeterBean</ejb-name>
          <home>org.garba.sessionbeans.GreeterHome</home>
          <remote>org.garba.sessionbeans.Greeter</remote>
          <ejb-class>org.garba.sessionbeans.GreeterBean</ejb-class>
          <session-type>Stateful</session-type>
          <transaction-type>Bean</transaction-type>
          <security-identity>
             <description></description>
             <use-caller-identity></use-caller-identity>
          </security-identity>
       </session>
    </enterprise-beans>
 </ejb-jar>

The Database

This is what the data in the store may look like.

customer_id name
1 Pasquale
2 Andrea
3 Rospone

The Client

In this example we use a client that is called from a servlet environment.

CustomerClient.java

01 package org.garba.tests;
02 
03 import java.io.PrintWriter;
04 import java.rmi.RemoteException;
05 
06 import javax.ejb.CreateException;
07 import javax.ejb.FinderException;
08 import javax.ejb.RemoveException;
09 import javax.naming.Context;
10 import javax.naming.InitialContext;
11 import javax.naming.NamingException;
12 import javax.rmi.PortableRemoteObject;
13 
14 import org.garba.entitybeans.Customer;
15 import org.garba.entitybeans.CustomerHome;
16 
17 public class CustomerClient {
18 
19 public final static void test(PrintWriter out) {
20 
21 try {
22 
23 Context ic = new InitialContext();
24 Object o = ic.lookup("java:comp/env/ejb/CustomerBean");
25 CustomerHome home = (CustomerHome) PortableRemoteObject.narrow(o, CustomerHome.class);
26 
27 // Find an existing entity
28 
29 Customer customer = home.findByPrimaryKey(new Integer(1));
30 out.println("Customer #1 name: " + customer.getName()); 
31 
32 // Create a new entity
33 
34 Customer newCustomer = home.create("Massimo");
35 out.println("New Customer Id: " + newCustomer.getCustomerId());
36 
37 // Remove new entity
38 
39 newCustomer.remove();
40 
41 } catch (NamingException e) {
42 out.println(e);
43 } catch (RemoteException e) {
44 out.println(e);
45 } catch (FinderException e) {
46 out.println(e);
47 } catch (CreateException e) {
48 out.println(e);
49 } catch (RemoveException e) {
50 out.println(e);
51 }
52 
53 }
54 
55 }

Result (Web output):

Customer #1 name: Pasquale
New Customer Id: 4                         `

Container’s console output (Resin 3.0.6 in this case):

1. The container puts a bean into the pool

Constructor (1)

setEntityContext (1)

2. home.findByPrimaryKey(new Integer(1));

Constructor (2)

setEntityContext (2)

ejbActivate (2)

ejbLoad (2)

3. home.create(“Massimo”);

Constructor (3)

setEntityContext (3)

ejbCreate (3)

ejbPostCreate (3)

4. newCustomer.remove();

ejbRemove (3)

unsetEntityContext (3)

The Client View

You can note the component interface extends EJBObject, and the home interface extends EJBHome, thus you are expected to find the same methods that session bean clients see. Of course, you will also find your own methods, such as getCustomerId/getName()/setName() in the component interface and findByPrimaryKey() in the home interface.

Key Moments in an Entity Bean’s Life


Please note that ejbSelectXXX() is invoked when the bean is in the pool if the call originates in the home. If the select method is called from a method in the bean’s component interface, the method is running on a specific entity, so the bean is in the method-ready state.

Creation

The container calls ejbCreate() only when the client requests the creation of a new entity that didn’t exist before. With “new entity” we refer to a new record in the storage (i.e. database). ejbCreate() doesn’t instantiate a new java object. Any bean from the pool may be returned. Remember that entity beans must not necessarily provide creation infrastructure. The bean developer chooses whether he wants to allow clients to create new entities.

Creation events in order

  1. ejbCreate()

    This is the only chance you have to modify your persistent fields before the material “insert” in the storage takes place. Thus, this is also the only place where you can set the entity’s primary key. Most Containers will complain if the primary key is null (we didn’t set it in our example) -this is just a proprietary Resin’s behavior with MySQL’s auto-generated primary keys. You cannot pass an EJBObject reference here because it still doesn’t exist.

    Rules:

    • Each ejbCreateXXX() in the bean class must match each createXXX() in the home interface.
    • The method name must begin with the prefix “ejbCreate()”.
    • The method must be declared public, and must not be declared static or final.
    • The declared return type must be the entity bean’s primary key type, even though you will return null from the method.
    • The method arguments must be the same as the arguments of the matching createXXX()
    • You may declare a throws clause with CreateException, or any arbitrary checked exception as long as it was also declared in the home interface.
    • You must not declare a RemoteException.


    Rules for primary keys:

    • A primary key class must be Serializable and public.
    • You can use a single persistent field from your bean class as your primary key, by identifying both the field name and the class type in the DD.
    • If you need two or more persistent field to uniqueley identify your entity, make a custom compound primary key class.
    • A compound key class must be made up of the fields that are defined as persistent fields in the bean class. The fields in the bean class must have public accessor methods.
  2. ejbPostCreate()

    At this time you have an EJBObject reference and you are able to access your CMR (container-managed relationships).

    Rules:

    • Each ejbPostCreateXXX() in the bean class must match each createXXX() in the home interface.
    • The method name must begin with the prefix “ejbPostCreate”.
    • The method must be declared public, and must not be declared static or final.
    • The declared return type must be void
    • The method arguments must be the same as the matching ejbCreateXXX().
    • You may declare a throws clause with CreateException, or any arbitrary checked exception as long as it was also declared in the home interface.
    • You must not declare a RemoteException.

Home Business Methods

Home Business Methods are used to interact with a group of entities rather than one specific entity. They don’t return the bean’s component interface as opposed to the Finder and Create methods.

In the Bean Class:

public Collection ejbHomeShowAll() { // Statements // ... }
In the Home Interface:

public Collection showAll() throws RemoteException {}

Rules for Home Business Methods

  1. There must be an ejbHomeXXX method in the bean class for every home method in the home interface
  2. The method must be declared public and NOT static.
  3. If the home interface is remote, the arguments and return values must be legal types for RMI-IIOP.
  4. Checked exceptions are allowed as long as they have been declared in the home interface.

Message-Driven Beans

Message-driven are stateless components for processing asynchronus JMS messages. They consume and process messages concurrently. Information and examples soon.

Key Moments in a Message-Driven Bean’s Life

EJB-QL

An EJB-QL statement is delcared by appending a query block to the entity declaration. EJB-SQL queries may receive parameters by declaring them in the <method-params> section and then by refering to them using ?1, ?2, ?3, etc. according to the number of parameters.

    <entity>
        ......
        ......
        <query>
            <query-method>
                <method-name>findEmployeesBySalary</method-name>
                <method-params>
                    <method-param>java.lang.Integer</method-param>
                </method-params>
            </query-method>
            <ejb-ql>
                SELECT DISTINCT OBJECT(e) FROM employees e WHERE e.salary >= ?1
            </ejb-ql>
        </query>
    </entity>

The table name should be written as in the <abstract-schema-name> and fields as in <field-name>

Finder Methods

Finder methods are declared in the home class and always use the prefix “find”. They are public and visible to the clients. As the default findByPrimaryKey() method implemented by the container, these methods may throw a FinderException.

In the Home Class:

   public Collection findEmployeesBySalary(Integer salary) throws FinderException;

Select Methods

Select methods are very much like finder methods but they are declared in the bean class and are private to the bean. They cannot be invoked by the client. Select methods must be declared abstract.

In ejb-jar.xml:

   <method-name>ejbSelectEmployeesBySalary</method-name>

In the Home Class:

   NOTHING!

In the Bean Class:

   public abstract Collection ejbSelectEmployeesBySalary(Integer salary) throws FinderException;

Examples

SELECT

  SELECT OBJECT (e) FROM employees e - Get a collection of all employee beans.
  SELECT e.name FROM employees e - Get as collection the name of all employees.
  SELECT OBJECT (e) FROM employees e WHERE e.cardNumber = 455433 - Get the employee who owns this credit card

THE IN OPERATOR

  SELECT DISTINCT OBJECT(b) FROM bosses b, IN (b.employees) e WHERE e.salary >= 500 - Select the bosses that have at least one employee that earns more than 500 bucks. The DISTINCT keyword ensures that the query does not return duplicates

ARITHMETIC OPERATORS

  SELECT OBJECT(e) FROM employees e WHERE (e.salary * 12) < 12000;

BETWEEN

  SELECT OBJECT(e) FROM employees e WHERE e.salary BETWEEN 6000 AND 8000;
  SELECT OBJECT(e) FROM employees e WHERE e.salary NOT BETWEEN 8000 AND 9000;

‘IN’ IN WHERE CLAUSE

  SELECT OBJECT(e) FROM employees e WHERE e.country IN ('de','es','it');

IS NULL

  SELECT OBJECT(e) FROM employees e WHERE e.wifeName IS NULL;
  SELECT OBJECT(e) FROM employees e WHERE e.wifeName IS NOT NULL;

IS EMPTY

  SELECT OBJECT(b) FROM bosses b WHERE b.employees IS EMPTY; - Checks for a boss without employees
  SELECT OBJECT(b) FROM bosses b WHERE b.employees IS NOT EMPTY; Checks for a boss with employees

Each boss has 0, 1 or more employees

Functional Expressions

References