Let's examine what a developer does to create enterprise beans using EJB 2.1 technology. The examples presented here are taken from the J2EE 1.4 Tutorial. The source code for the examples is contained in the J2EE 1.4 tutorial bundle. If you download the tutorial bundle, you'll find the example source code below the
directory, where <install_dir
> is the directory where you installed the tutorial bundle.
Note that the remainder of this article focuses on session beans and enterprise beans. Message-driven beans will not be specifically covered. Some of the same development techniques described in this section for stateless session beans also apply to message-driven beans. Also, many of the development simplifications and new features provided in EJB 3.0 technology are also applicable to message-driven beans. For information on how to develop message-driven beans in EJB 2.1 technology see Chapter 28: A Message-Driven Bean Example in the J2EE 1.4 Tutorial.
A Stateless Session Bean
Let's start with an example of a stateless session bean called ConverterBean
. This is an enterprise bean that can be accessed remotely. The bean converts currency -- from dollars to yen, and from yen to euros. The source code for ConverterBean
is in the
directory.
To create the session bean, a developer needs to code:
- A home interface that defines the life-cycle methods for the session bean that can be accessed
by a remote client. - A remote interface that defines the business methods for the session bean that can be accessed by a remote client.
- A bean class that contains the implementations of the home and remote interfaces.
- A deployment descriptor that specifies information about the bean such as it's name and type.
Home Interface
Here's the home interface for ConverterBean
:
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface ConverterHome extends EJBHome {
Converter create() throws RemoteException, CreateException;
}
The home interface:
- Extends the
javax.ejb.EJBHome
interface. All home interfaces must extendjavax.ejb.EJBHome
. - Defines a life-cycle method,
create
. A home interface must define one or more life cycle methods, such ascreate
andremove
, and these methods can have multiple signatures. In the case of a stateless session bean, thecreate
method cannot have arguments. A remote client can invoke thecreate
method to request creation of a session bean instance. In response, the EJB container creates the instance within the container (that is, on the server). Thecreate
method returns an object that is the remote interface type of the bean, in this example,Converter
. This object runs locally on the client and acts as a proxy for the remote instance of the bean. A client can subsequently invoke business methods on theConverter
object locally. In response, this invokes the same business methods on the remote session bean instance in the EJB container. Thecreate
method throws aRemoteException
and aCreateException
.
Remote Interface
Here's the remote interface for ConverterBean
:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.math.*;
public interface Converter extends EJBObject {
public BigDecimal dollarToYen(BigDecimal dollars)
throws RemoteException;
public BigDecimal yenToEuro(BigDecimal yen)
throws RemoteException;
}
The remote interface:
- Extends the
javax.ejb.EJBObject
interface. All remote interfaces must extend thejavax.ejb.EJBObject
interface. - Defines the business methods for the bean, in this case,
dollarToYen
andyenToEuro
. These are the methods that the remote client can call on the session bean. However, as mentioned in the description of the home interface, these method calls actually go through a local proxy object that subsequently invokes analogous methods on the remote session bean instance in the EJB container. Each business method throws aRemoteException
.
Bean Class
Here's the class for ConverterBean
:
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.math.*;
public class ConverterBean implements SessionBean {
BigDecimal yenRate = new BigDecimal("121.6000");
BigDecimal euroRate = new BigDecimal("0.0077");
public BigDecimal dollarToYen(BigDecimal dollars) {
BigDecimal result = dollars.multiply(yenRate);
return result.setScale(2,BigDecimal.ROUND_UP);
}
public BigDecimal yenToEuro(BigDecimal yen) {
BigDecimal result = yen.multiply(euroRate);
return result.setScale(2,BigDecimal.ROUND_UP);
}
public ConverterBean() {}
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}
}
The bean class:
- Implements the
javax.ejb.SessionBean
interface. Because the session bean implements this interface, it must also implement all of the methods in the interface:ejbRemove
,ejbActivate
,ejbPassivate
, andsetSessionContext
. The class must implement these methods even if it doesn't use them, as is the case here because the methods are empty. - Defines a constructor with no parameters.
- Implements an
ejbCreate
method. A session bean must implement at least oneejbCreate
method. It could implement more, but each must have a different signature. For everyejbCreate
method implemented in the class, there must be a correspondingcreate
method defined in the home interface. Recall that a client can't directly call a session or entity bean's methods, and instead calls methods defined in the bean's interfaces. To create an instance of the session bean, a remote client calls thecreate
method on the home interface. In response, the EJB container instantiates the session bean and then invokes the correspondingejbCreate
method in the bean class to initialize the instance's state. In this example,ejbCreate
is empty so there is no initial setting of data in the bean instance. - Implements the business methods defined in the remote interface:
dollarToYen
andyenToEuro
. A remote client invokes business methods on the remote interface (through the local proxy). In response, this invokes the same business methods on the remote session bean component in the EJB container. The container then runs the business method implementations in the bean class.
Deployment Descriptor
Here's the deployment descriptor for ConverterBean
:
ConverterJAR
ConverterBean
converter.ConverterHome
converter.Converter
converter.ConverterBean
Stateless
Bean
The deployment descriptor is an XML file that specifies basic information about
the bean, such as it's name and the name of its interfaces. In fact, the descriptor's
purpose is mainly to associate the bean class with the interfaces. The descriptor in this
example also identifies this as a stateless session bean.
The Session Bean Client
To demonstrate how the bean is accessed, here's the remote client provided in the ConverterBean
example inside the J2EE 1.4 tutorial bundle:
import converter.Converter;
import converter.ConverterHome;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.math.BigDecimal;
public class ConverterClient {
public static void main(String[] args) {
try {
Context initial = new InitialContext();
Context myEnv = (Context) initial.lookup("java:comp/env");
Object objref = myEnv.lookup("ejb/SimpleConverter");
ConverterHome home =
(ConverterHome) PortableRemoteObject.narrow(objref,
ConverterHome.class);
Converter currencyConverter = home.create();
BigDecimal param = new BigDecimal("100.00");
BigDecimal amount = currencyConverter.dollarToYen(param);
System.out.println(amount);
amount = currencyConverter.yenToEuro(param);
System.out.println(amount);
System.exit(0);
} catch (Exception ex) {
System.err.println("Caught an unexpected exception!");
ex.printStackTrace();
}
}
}
The initial part of the code creates an instance of the session bean. Recall that a client can't directly call a session bean's methods, and instead calls methods defined in the bean's interfaces. To create an instance of the session bean, the client needs to call the create
method on the bean's home interface. To do that, the client first uses the Java Naming and Directory Interface (JNDI) to acquire an object that represents the ConverterHome
interface. The client then invokes the create
method on the ConverterHome
object. In response, the EJB container creates the session bean instance and then invokes the corresponding ejbCreate
method in the bean class to initialize the instance's state. In this example, ejbCreate
is empty so there is no initial setting of data in the bean instance. The container returns a Converter
object. To invoke a business method, the client calls the business method on the Converter
object.
An Entity Bean with Container-Managed Persistence
For the second example, let's examine an entity bean called PlayerBean
that represents a player on a sports team. PlayerBean
uses container-managed persistence. In addition, it has relationships with other entity beans, such as TeamBean
. The TeamBean
entity bean represents a sports team. The relationship to TeamBean
is many-to-many: a player can be on multiple teams (in different sports), and a team has multiple players. These relationships are maintained by the container. The source code for PlayerBean
is in the
directory.
An entity bean like PlayerBean
that is the target of a container-managed relationship must have a local interface. So PlayerBean
is accessible by local clients only. To create the entity bean, a developer needs to code:
- A local home interface that defines the life-cycle methods for the entity bean that can be invoked by local clients.
- A local interface that defines the business and access methods for the entity bean that can be invoked by local clients.
- A bean class that contains the implementation of the business logic for the local home and local interfaces.
- A deployment descriptor that specifies information about the bean such as its name, its persistence type, and its abstract schema.
Local Home Interface
Here's an excerpt from the local home interface for PlayerBean
:
package team;
import java.util.*;
import javax.ejb.*;
public interface LocalPlayerHome extends EJBLocalHome {
public LocalPlayer create (String id, String name,
String position, double salary)
throws CreateException;
public LocalPlayer findByPrimaryKey (String id)
throws FinderException;
public Collection findByPosition(String position)
throws FinderException;
...
public Collection findBySport(String sport)
throws FinderException;
...
}
The local home interface:
- Extends the
javax.ejb.EJBLocalHome
interface. All local home interfaces must
extendjavax.ejb.EJBLocalHome
. - Defines a life-cycle method,
create
. As is the case for home interfaces, a local home interface must define one or more life cycle methods, such ascreate
andremove
. However unlikemethods for stateless sessions beans, a create
method for an entity bean can have arguments. A client can invoke thecreate
method to request creation of an entity bean instance. In response, the EJB container creates the instance within the container (that is, on the server). Thecreate
method returns an object that is the local interface type of the entity bean, in this example,LocalPlayer
. It also throws aCreateException
. (Because this is a local interface, thecreate
method doesn't throw aRemoteException
.) - Defines finder methods. These are methods whose name begins with
find
. A local home interface for an entity bean must define at least thefindByPrimaryKey
method. All finder methods exceptfindByPrimaryKey
use EJB QL queries (defined in the deployment descriptor for the bean) to find instances of an entity bean based on specific criteria. For example,findBySport
uses an EJB QL query to find instances ofPlayerBean
that represent players in a specified sport. Finder methods return the local interface type of the entity bean or a collection of those types. They also throw aFinderException
.
Although not shown in this example, a local home interface can also define home methods. A home method is similar to a business method in that it contains business logic, but unlike a business method, a home method applies to all entity beans of a particular class. (A business method applies to a single entity bean.)
Local Interface
Here's the local interface for PlayerBean
:
package team;
import java.util.*;
import javax.ejb.*;
public interface LocalPlayer extends EJBLocalObject {
public String getPlayerId();
public String getName();
public String getPosition();
public double getSalary();
public Collection getTeams();
public Collection getLeagues() throws FinderException;
public Collection getSports() throws FinderException;
}
The local interface:
- Extends the
javax.ejb.EJBLocalObject
interface. All local interfaces must extend thejavax.ejb.EJBLocalObject
interface. - Defines the access methods that a local client can invoke, in this case,
getPlayerID
,getName
,getPosition
,getSalary
, andgetTeams
. These methods access the bean's persistent fields (playerID
,name
,position
, andsalary
) and relationship field (teams
) that are specified in the bean's deployment descriptor. - Defines business methods that a local client can invoke on the entity bean, in this case,
getLeagues
andgetSports
.
Bean Class
Here's an excerpt from the PlayerBean
class:
public abstract class PlayerBean implements EntityBean {
private EntityContext context;
public abstract String getPlayerId();
public abstract void setPlayerId(String id);
...
public abstract Collection getTeams();
public abstract void setTeams(Collection teams);
...
public abstract Collection ejbSelectLeagues(LocalPlayer player)
throws FinderException;
...
public Collection getLeagues() throws FinderException {
LocalPlayer player =
(team.LocalPlayer)context.getEJBLocalObject();
return ejbSelectLeagues(player);
}
...
public String ejbCreate (String id, String name,
String position, double salary) throws CreateException {
setPlayerId(id);
setName(name);
setPosition(position);
setSalary(salary);
return null;
}
public void ejbPostCreate(String id, String name, String position,
double salary) throws CreateException {
}
...
The bean class:
- Implements the
javax.ejb.EntityBean
interface. Because the entity bean implements this interface, it must also implement all of the methods in the interface:ejbRemove
,ejbActivate
,ejbPassivate
,ejbLoad
,ejbStore
,setEntityContext
andunsetEntityContext
. The class must implement these methods even if it doesn't use them. - Defines access methods such as
getPlayerID
andsetPlayerID
that access the bean's persistent fields, and methods such asgetTeams
andsetTeams
that access the bean's relationship field. Entity bean fields are identified as persistent fields or relationship fields in the bean's deployment descriptor. Notice that the access methods are defined asabstract
. That's because the EJB container actually implements the methods. In other words, the container actually gets and sets the data for these fields. - Defines select methods, such as
ejbSelectLeagues
. These are methods that begin withejbSelect
. Select methods are similar to finder methods. For example, each select method requires a corresponding EJB QL query in the deployment descriptor. However unlike finder methods, select methods cannot be invoked by a client. They can be invoked only by the methods implemented in the entity bean class. In thePlayerBean
class, the select methods appear within business methods (which the local client can invoke). Unlike select methods, finder methods are not defined in the bean class. - Implements business methods, such as
getLeagues
. In thePlayerBean
class, these methods are essentially wrappers for select methods. - Implements an
ejbCreate
method and anejbPostCreate
method. To create an instance of the entity bean, a local client calls thecreate
method on the local home interface. In response, the EJB container instantiates the entity bean and then invokes the correspondingejbCreate
method in the bean class. The container initializes the instance's state by calling theset
access methods inejbCreate
to assign the input arguments to the persistent fields. At the end of the transaction that contains the create call, the container saves the persistent fields by inserting a row into the database. The container also calls methodejbPostCreate
after the component is created. This method can be used to do things such as set a relationship field to initialize the bean instance. However, in this example, the method is empty.
Deployment Descriptor
Here's an excerpt from the deployment descriptor for PlayerBean
:
TeamJAR
PlayerBean
team.LocalPlayerHome
team.LocalPlayer
team.PlayerBean
Container
...
Player
no description
position
...
playerId
...
findAll
select object(p) from Player p
...
ejbSelectLeagues
team.LocalPlayer
Local
select distinct t.league
from Player p, in (p.teams) as t
where p = ?1
...
Many
TeamBean
players
java.util.Collection
Many
PlayerBean
teams
java.util.Collection
...
Clearly there's a lot more here than in the deployment descriptor for the stateless session bean in the previous example. In addition to the basic information it specifies about the entity bean, such as it's name and the name of its interfaces, the deployment descriptor specifies the bean's abstract schema. The abstract schema for the bean identifies the bean's container-managed persistence fields, such as position
, and the bean's container-managed relationships, such as the many-to-many relationship between players
and teams
. The deployment descriptor also specifies EJB QL queries, such as select object(p) from Player p
, and the finder method each query is associated with (in this case, findAll
). Additionally, the deployment descriptor associates EJB QL queries with select methods.
The Entity Bean Client and Remote Session Bean
To demonstrate how the entity bean is accessed, here are some excerpts from the local client and from a remote session bean named RosterBean
. The client and the session bean are provided in the ConverterBean
example inside the J2EE 1.4 tutorial bundle. The client invokes methods on RosterBean
, which works in conjunction with entity beans local to it, such as PlayerBean
and TeamBean
, to access needed data.
Here is the client:
import java.util.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import util.*;
import roster.*;
public class RosterClient {
public static void main(String[] args) {
try {
Context initial = new InitialContext();
Object objref = initial.lookup("java:comp/env/ejb/SimpleRoster");
RosterHome home =
(RosterHome) PortableRemoteObject.narrow(objref,
RosterHome.class);
Roster myRoster = home.create();
The client uses JNDI to acquire an object that represents the session bean's remote interface, RosterHome
. The client then invokes the create
method on the RosterHome
object. In response, the EJB container creates the session bean instance, and invokes a corresponding ejbCreate
method in the RosterBean
class to initialize the instance's state.
The client invokes the createPlayer
method of RosterBean
to create a new player:
myRoster.createPlayer(new PlayerDetails("P1", "Phil Jones",
"goalkeeper", 100.00));
Here is the createPlayer
method in RosterBean
:
public void createPlayer(PlayerDetails details) {
try {
LocalPlayer player = playerHome.create(details.getId(),
details.getName(), details.getPosition(),
details.getSalary());
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
The client then calls the addPlayer
method of RosterBean
to add player P1
to team T1
:
myRoster.addPlayer("P1", "T1");
The P1
and T1
parameters are the primary keys of the PlayerBean
and TeamBean
instances.
The client invokes the getPlayersByPosition
method of PlayerBean
to create a new player.
playerList = myRoster.getPlayersByPosition("defender");
The local home interface for PlayerBean
defines the finder method, findByPosition
, as follows:
public Collection findByPosition(String position)
throws FinderException;
The EJB container then executes the EJB QL query associated with findByPosition
in the bean's deployment descriptor. The EJB QL query is:
SELECT DISTINCT OBJECT(p) FROM Player p
WHERE p.position = ?1
So What's the Problem?
Looking at the EJB 2.1 examples, it's not too hard to understand why some developers see EJB technology as overly complex. Some of the things that feed this perception are:
- The need to code a variety of classes, interfaces, and files for an enterprise bean. For even a "simple" enterprise bean, a developer needs to code two interfaces (a home and component interface), a bean class, and a deployment descriptor.
- The size and complexity of the deployment descriptor. Notice especially the size and content of the deployment descriptor for the entity bean with container-managed persistence in the previous example. Beyond that, the deployment descriptor contains a lot of information that appears in the interfaces and class of the bean -- in other words, the deployment descriptor contains a lot of redundant information.
- The many rules that need to be followed. EJB technology is quite prescriptive. Rules such as "all home interfaces must extend
javax.ejb.EJBHome
" pervade the technology. - The need to implement all interface methods. Because an enterprise bean class implements an interface, it must also implement all of the methods in the interface, such as
ejbActivate
andejbPassivate
. This is required even if the bean doesn't use the interface methods. This adds to what many developers see as "code clutter." - The use of JNDI to locate and acquire enterprise bean objects. Some developers view JNDI as clumsy and non-intuitive.
EJB 3.0 technology aims to address these and other problems that contribute to perceived complexity.