Enterprise JavaBeans (EJB) Guide
A guide to the old Enterprise JavaBeans (EJB) framework.
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
- Represents a thing in a persistent store. Usually represents a row in a table.
- An entity bean is something.
Session Bean
- It typically represents a process.
- A session bean does something.
- There are two kinds of Session Beans:
- STATELESS
- Can’t remember anything between method calls.
- Forgets about the client once the method call completes.
- Is more scalable than a statefull session bean.
- STATEFUL
- Can remember conversation state or client-specific state.
Message-driven Bean
- Can listen for messages from a JMS messaging service.
- Clients never call message-driven beans directly.
- Has no EJBObject because the requests come from the messaging service.
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
- It must import javax.ejb.*
- It must extend EJBLocalHome.
- All create methods must declare a CreateException.
- All create methods must return a local component interface.
- Your own exceptions should be checked exceptions (Not subclasses of RuntimeException)
- You must not declare a RemoteException for any methods
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
- It must import javax.ejb.*
- It must extend EJBLocalObject.
- You must declare one or more methods.
- Your own exceptions should be checked exceptions (Not subclasses of RuntimeException).
- Business methods names must not begin with the string “ejb”.
- You must not declare a RemoteException for any methods.
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:
- Lines 1 to 7 (Import statements): There is no need for javax.rmi.*; or java.rmi.*;
- Line 24: Narrowing is no longer necessary.
- Lines 27 to 31 (Catch statement): Catching RemoteException is no longer necessary.
- Line 35: The method getGreeting() doesn’t declare a RemoteException so there’s no need to catch it.
Key Moments in a Session Bean’s Life
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
- Object construction
- setSessionContext()
- 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
- ejbPassivate()
- 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:
- a Serializable object
- a null value
- a bean’s remote component or home interface (regardless of the serialization state of the stub’s class)
- a bean’s local component or home interface (regardless of its serialization state)
- a SessionContext object
- the bean’s special JNDI context and its subcontexts
- the UserTransaction interface
- a resource manager connection factory (like, an instance of javax.sql.DataSource)
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="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 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
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.
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
- There must be an ejbHomeXXX method in the bean class for every home method in the home interface
- The method must be declared public and NOT static.
- If the home interface is remote, the arguments and return values must be legal types for RMI-IIOP.
- 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
- CONCAT(String1, String2) - String1 + String 2
- LENGTH(String)
- LOCATE(String1, String2 [, start]) - Tells in which position String1 is found within String2. start is optional and indicates the position in String2 at which the search should start.
- SUBSTRING(String1, start, length) - SUBSTRING(“neos bastardo”,2,3) = eos
- ABS(number) - Absolute value of a number of type (int, float or double)
- SQRT(double) - Square root
References
- Head First EJB first edition by Kathy Sierra & Bert Bates. ISBN 0-596-00571-7 (Oct 2003, O’REILLY)
- Enterprise JavaBeans 3rd Edition by Richard Monson-Haefel. ISBN 0-596-00226-2 (September 2001, O’REILLY)
- Caucho’s Resin documentation