Articles
Java Platform, Standard Edition
|
| By Ed Ort, October 2004 |
|
| |
|
Enterprise JavaBeans (EJB) technology is a J2EE technology for developing business components in a component-based, enterprise Java application. Business components developed with EJB technology are often called Enterprise JavaBeans components or simply "enterprise beans." Enterprise beans typically provide the logic and represent the data needed to perform operations specific to a business area such as banking or retail. For example, an enterprise bean (perhaps together with other enterprise beans) might offer the data and logic needed to perform banking account operations, such as crediting and debiting an account. Other enterprise beans might offer the data and logic needed to perform "shopping cart" operations that allow customers to purchase goods online from a retail store.
EJB technology is generally viewed as powerful and sophisticated. The technology helps developers build business applications that meet the heavy-duty needs of an enterprise. In particular, applications built using EJB and other J2EE technologies are secure, scale to support very large numbers of simultaneous users, and are transaction-enabled so that data maintains its integrity even though it's processed concurrently by multiple users.
These needs are met through services that are provided by the EJB runtime environment -- the EJB container. What this means is that support for things like concurrent data access and security is automatically provided by the EJB container to all enterprise beans.
Despite its power and sophistication, some developers are hesitant to use EJB technology. The major obstacle for these developers is complexity. But there's good news in store. The next release of the Enterprise JavaBeans architecture, EJB 3.0, focuses primarily on making the technology easier for developers to use -- but not at the expense of the technology's power and sophistication. (In addition, the EJB 2.0 and 2.1 APIs are still available for use.) The idea is not only to make EJB technology easier to use for developers who already use it, but to attract a wider range of developers.
|
This article covers some of the ease-of-development features that are being planned for EJB 3.0 and that are documented in the EJB 3.0 Specification (now available as an early draft). It's important to note that the EJB 3.0 Specification is a work in progress, and so might change over time.
But first, let's take a step back and look at some of the basics of EJB technology. Then let's look at how the technology works today. In other words, let's examine what a developer does to create and use enterprise beans with the current level of the technology, EJB 2.1. After that let's examine how EJB 3.0 simplifies things for developers.
| |
This section gives a brief overview of some of the key concepts related to EJB technology. A lot of the material in this section is extracted from the J2EE 1.4 Tutorial, which is an excellent resource for learning more about EJB terminology and concepts. See especially Chapter 23: Enterprise Beans for a more detailed introduction to Enterprise JavaBeans technology.
|
As mentioned earlier, enterprise beans provide the logic and represent the data for business-oriented operations. Enterprise beans are portable -- you can deploy them in any compliant J2EE application server. This gives developers a lot of flexibility in terms of distributing the components of an application. Enterprise beans are also reusable -- you can use them in multiple business applications. But perhaps what's most appealing about enterprise beans is that they're managed. The EJB container provides system-level services such as transaction management (to preserve the integrity of data in a multi-user environment) and security management (to protect against unauthorized access to resources). Because the EJB container provides these and other system-level services, a developer does not have to provide them in an application, and so is free to concentrate on developing the logic and data aspects of the application.
There are three types of enterprise beans: session beans, entity beans, and message-driven beans. Often a combination of these beans is used in an enterprise application.
Session Beans
|
A session bean represents a single unique session between a client and an instance of the bean. A session bean can't be shared. One instance of the bean is tied to a specific client in a specific session. The session bean exposes methods that a client can call to execute business tasks on the server. When the client's session ends, the session bean is no longer associated with that client.
There are two types of session beans: stateful and stateless. A stateful session bean maintains data about the unique client-bean session in its instance variables. The data represents the state (often called the "conversational state") of that specific session. The conversational state is maintained for the life of the client-bean association. Significantly, this means that the data is maintained across operations. If one of the bean's methods finishes executing and another is called during the session, the conversational state is maintained from one operation to the next. This makes stateful session beans useful for things like shopping cart and banking services that involve multiple, related operations. To maintain conversational state when space is constrained, the EJB container might write a stateful session bean to secondary storage and then later retrieve it.
By comparison, a stateless session bean does not maintain conversational state for its client. Because a stateless session bean cannot maintain conversational state across methods, it's typically used for one-step tasks, such as sending an email that confirms an online order.
An EJB container never writes a stateless session bean to secondary storage. One other distinguishing thing about a stateless session bean is that it can implement a web service.
Entity Beans
|
An entity bean represents data in a storage medium, such as a relational database. Each entity bean may correspond to a table in a relational database, and each instance of the bean corresponds to a row in that table. Entity beans are not limited to representing relational databases. They can represent data in other types of data stores. But the majority of enterprise applications that use EJB technology access data in relational databases, so this article focuses on that type of data store.
The emphasis here is on the word "represents." An entity bean in not part of a relational database, but rather contains data that is loaded from the database at appropriate points in time. The data is then written back to the database at other appropriate points in time.
An entity bean differs from a session bean in several ways. An entity bean can be shared (a session bean cannot), that is, multiple clients can use the same entity bean. The gives multiple clients the ability to access the same data that the bean represents. Of course, this is where the transaction management (and its data integrity control) provided by the EJB container is important. In addition, entity beans are persistent. This means that the entity bean's state exists even after the application that uses it ends. The bean is persistent because the database that underlies it is persistent. For that matter, the entity bean's state can be reconstructed even after a server crash (because the database can be reconstructed). Remember that the state of a stateful session bean exists only for the life of the client-bean session, and for a stateless session bean, only during the life of a method. Also, an entity bean can have relationships with other entity beans, much like a table in a relational database can be related to another table. For example, in a college enrollment application, an entity bean that represents data about students might be related to an entity bean that represents data about classes. By comparison, a session bean cannot be related to other session beans.
An entity bean can manage its own persistence (this is called bean-managed persistence) or let the EJB container manage it (container-managed persistence). With bean-managed persistence, the entity bean code includes SQL statements that access the database. With container-managed persistence, the EJB container automatically generates the necessary database access calls.
Message-Driven Beans
|
A message-driven bean processes asynchronous messages typically sent through the Java Message Service (JMS) API. Asynchronous messaging frees the message sender from waiting for a response from the message receiver.
A message-driven bean can process messages sent by any J2EE component (such as an application client, another enterprise bean, or a web component) or by a JMS application or system that does not use J2EE technology. Often message-driven beans are used to route messages. This makes them useful in many business-to-business communication scenarios.
Enterprise Bean Interfaces and Classes
Although it's easy to imagine a client directly calling a bean's methods, the reality is that it can't. Instead, for session or entity beans, a client calls methods defined in the bean's interfaces. These interfaces comprise the client's "view" of the bean. A session or entity bean must have two interfaces -- a component interface and a home interface -- as well as a bean class. Neither can a client access a message-driven bean directly -- it needs to send messages to the message endpoint serviced by the bean.
The component interface defines the business methods for the bean. For example, the component interface for a session bean that handles bank accounts might define a method that credits an account and one that debits an account. The home interface defines "life-cycle" methods for the bean. These are methods that do things like create an instance of a bean or remove it (or at least make it unavailable). For entity beans, the home interface can also define finder methods and home methods. Finder methods locate instances of a bean. Home methods are business methods that are invoked on all instances of an entity bean class.
The bean class implements the methods defined in the bean's interfaces.
Local and Remote Enterprise Beans
Clients can access beans locally, that is, where the client and bean are in the same Java virtual machine 1 , or remotely, where the client and bean can be in different Java virtual machines (or even different physical machines). A local or remote client can be a web component or another enterprise bean. A remote client can also be a web application. For local access, a session or entity bean must provide a component interface called a local interface, and a home interface called a local home interface. For remote access, a session or entity bean must provide a component interface called a remote interface, and a home interface.
The EJB Container and Persistence
Clearly, the EJB container plays an important role in Enterprise JavaBeans technology. It provides the runtime environment inside of an application server for enterprise beans. In doing that, it offers a wide variety of services to the beans, such as transaction management and security control. In addition, it manages persistence for entity beans with container-managed persistence, and relationships for entity beans with container-managed relationships. To manage persistence and relationships, the EJB container needs information beyond the information provided in the bean's interfaces and classes. This additional information is provided in an abstract schema that defines the bean's persistent fields (the fields that represent persistent data) and the bean's relationships. An abstract schema differs from the physical schema of the underlying database (which defines the table structures in the database and the table relationships). In EJB 2.0 and EJB 2.1 technology, you need to provide deployment descriptor information for each entity bean. The deployment descriptor specifies, among other things, the abstract schema for the bean (see Developing an Enterprise Bean with EJB 2.1 Technology, for an example).
EJB Query Language
Recall that an entity bean with container-managed persistence does not include SQL statements. So you might wonder, how does the EJB container know what persistent data to access and what to return? The answer is through finder methods and Enterprise JavaBeans Query Language (EJB QL) queries. Finder methods find instances of an entity bean or collections of entity beans. EJB QL queries query the abstract schema. You specify finder methods in the home interface of the bean, and associate them with queries written in EJB QL. An EJB QL query looks like an SQL query -- it contains a SELECT clause, a FROM clause, and a WHERE clause. However, unlike an SQL-based query, an EJB QL query queries the abstract schema, not the database. When a finder method is invoked, the EJB container executes the EJB QL query associated with that method and returns references to one or more entity beans that represent data satisfying the query criteria. An example of a finder method might be one that identifies bank accounts that have an account balance that is less than a certain threshold. The EJB QL query for that finder method queries the abstract schema and returns references to one or more entity beans that represent accounts meeting the query criteria. In addition to the abstract schema it defines, the deployment descriptor for an entity bean in EJB 2.1 technology defines the EJB QL queries for the bean and associates the queries with finder methods.
| |
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
<install_dir>/j2eetutorial14/examples/ejb 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
<install_dir>/j2eetutorial14/examples/ejb/converter/src directory.
To create the session bean, a developer needs to code:
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:
javax.ejb.EJBHome interface. All home interfaces must extend
javax.ejb.EJBHome.
create. A home interface must define one or more life cycle methods, such as
create and
remove, and these methods can have multiple signatures. In the case of a stateless session bean, the
create method cannot have arguments. A remote client can invoke the
create 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). The
create 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 the
Converter object locally. In response, this invokes the same business methods on the remote session bean instance in the EJB container. The
create method throws a
RemoteException and a
CreateException.
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:
javax.ejb.EJBObject interface. All remote interfaces must extend the
javax.ejb.EJBObject interface.
dollarToYen and
yenToEuro. 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 a
RemoteException.
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:
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, and
setSessionContext. The class must implement these methods even if it doesn't use them, as is the case here because the methods are empty.
ejbCreate method. A session bean must implement at least one
ejbCreate method. It could implement more, but each must have a different signature. For every
ejbCreate method implemented in the class, there must be a corresponding
create 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 the
create method on the home interface. In response, the EJB container instantiates the session bean 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.
dollarToYen and
yenToEuro. 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:
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" ..."> <display-name>ConverterJAR</display-name> <enterprise-beans> <session> <ejb-name>ConverterBean</ejb-name> <home>converter.ConverterHome</home> <remote>converter.Converter</remote> <ejb-class>converter.ConverterBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Bean</transaction-type> <security-identity> <use-caller-identity/> </security-identity> </session> </enterprise-beans> </ejb-jar>
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
<install_dir>/j2eetutorial14/examples/ejb/cmproster/src 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:
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:
javax.ejb.EJBLocalHome interface. All local home interfaces must extend
javax.ejb.EJBLocalHome.
create. As is the case for home interfaces, a local home interface must define one or more life cycle methods, such as
create and
remove. However unlike methods for stateless sessions beans, a
create method for an entity bean can have arguments. A client can invoke the
create 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). The
create method returns an object that is the local interface type of the entity bean, in this example,
LocalPlayer. It also throws a
CreateException. (Because this is a local interface, the
create method doesn't throw a
RemoteException.)
find. A local home interface for an entity bean must define at least the
findByPrimaryKey method. All finder methods except
findByPrimaryKey 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 of
PlayerBean 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 a
FinderException.
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:
javax.ejb.EJBLocalObject interface. All local interfaces must extend the
javax.ejb.EJBLocalObject interface.
getPlayerID,
getName,
getPosition,
getSalary, and
getTeams. These methods access the bean's persistent fields (
playerID,
name,
position, and
salary) and relationship field (
teams) that are specified in the bean's deployment descriptor.
getLeagues and
getSports.
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:
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 and
unsetEntityContext. The class must implement these methods even if it doesn't use them.
getPlayerID and
setPlayerID that access the bean's persistent fields, and methods such as
getTeams and
setTeams 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 as
abstract. That's because the EJB container actually implements the methods. In other words, the container actually gets and sets the data for these fields.
ejbSelectLeagues. These are methods that begin with
ejbSelect. 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 the
PlayerBean 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.
getLeagues. In the
PlayerBean class, these methods are essentially wrappers for select methods.
ejbCreate method and an
ejbPostCreate method. To create an instance of the entity bean, a local client calls the
create method on the local home interface. In response, the EJB container instantiates the entity bean and then invokes the corresponding
ejbCreate method in the bean class. The container initializes the instance's state by calling the
set access methods in
ejbCreate 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 method
ejbPostCreate 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:
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" ..."> <display-name>TeamJAR</display-name> <enterprise-beans> <entity> <ejb-name>PlayerBean</ejb-name> <local-home>team.LocalPlayerHome</local-home> <local>team.LocalPlayer</local> <ejb-class>team.PlayerBean</ejb-class> <persistence-type>Container</persistence-type> ... <abstract-schema-name>Player</abstract-schema-name> <cmp-field> <description>no description</description> <field-name>position</field-name> </cmp-field> ... <primkey-field>playerId</primkey-field> ... <query> <query-method> <method-name>findAll</method-name> <method-params/> </query-method> <ejb-ql>select object(p) from Player p</ejb-ql> </query> ... <query> <query-method> <method-name>ejbSelectLeagues</method-name> <method-params> <method-param>team.LocalPlayer</method-param> </method-param> </query-method> <result-type-mapping>Local</result-type-mapping> <ejb-ql>select distinct t.league from Player p, in (p.teams) as t where p = ?1</ejb-ql> </query> ... </entity> </enterprise-beans> <relationships> <ejb-relation> <ejb-relationship-role> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>TeamBean</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>players</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>PlayerBean</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>teams</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> </ejb-relation> ... </ejb-relationship-role> </ejb-relation> </relationships> </ejb-jar>
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:
javax.ejb.EJBHome" pervade the technology.
ejbActivate and
ejbPassivate. This is required even if the bean doesn't use the interface methods. This adds to what many developers see as "code clutter."
EJB 3.0 technology aims to address these and other problems that contribute to perceived complexity.
| |
Let's look at what a developer does to create enterprise beans using EJB 3.0 technology. Note that the material in this section reflects the technology as documented in the early draft of the EJB 3.0 Specification. The specification might change over time, and so some aspects of what a developer codes to create enterprise beans might also change.
A Stateless Session Bean
|
Let's examine what a developer needs to do to develop a stateless session bean using EJB 3.0 technology. Once again, let's develop a bean named
Converter that converts currency -- from dollars to yen, and from yen to euros-- and that can be accessed remotely. This is the same stateless session bean presented in
Developing an Enterprise Bean with EJB 2.1 Technology. You'll see how much easier it is to develop the bean using EJB 3.0 technology.
To create the session bean, a developer only needs to code a bean class and annotate it with appropriate metadata annotations:
@Stateless @Remote public class ConverterBean {
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);
}
}
In this example, the metadata annotations are
@Stateless and
@Remote. Significantly, there's no home interface, remote interface, or deployment descriptor to code. All that's required in addition to the bean class is a
business interface, and that can be generated by default (as is the case here).
The bean class is coded as a plain old Java object ("POJO") rather than a class that implements an interface such as
javax.ejb.SessionBean. Because the bean class doesn't implement an interface such as
javax.ejb.SessionBean, a developer no longer has to implement methods such as
ejbRemove,
ejbActivate, or
ejbPassivate in the bean class. However a developer can implement any or all of these callbacks if they are needed. If the bean class implements one of these callbacks, the EJB container calls it just as it does for EJB 2.1 technology.
Metadata Annotation
Metadata annotation is a new Java Language feature in Java 2 Platform Standard Edition (J2SE) 5.0. It's designed to give developers a way to annotate code at appropriate points in a program. A developer can define an annotation type, for example:
public @interface Copyright {
String value ();
}
An annotation type is defined with an
@ sign preceding the
interface keyword. The annotation type includes one or more elements -- in this case,
value. (In annotations with a single element, the element should be named
value to avoid having to specify the name when the annotation is used.) After an annotation type is defined, a developer can use it to annotate declarations in code:
@Copyright("2004 My Corporation")
public class Converter {
...
An annotation consists of the
@ sign preceding the annotation type, followed by a parenthesized list of element-value pairs. When an annotation processing tool (there's one in J2SE 5.0) encounters the annotation, it generates the code for the annotation -- here, a copyright notice that includes the string "2004 My Corporation".
The EJB 3.0 Specification defines a variety of annotation types such as those that specify a bean's type (
@Stateless,
@Stateful,
@MessageDriven,
@Entity), whether a bean is remotely or locally accessible (
@Remote,
@Local), transaction attributes (
@TransactionAttribute), and security and method permissions (
@MethodPermissions,
@Unchecked,
@SecurityRoles). (There are many more annotations defined in the specification than these.) Annotations for the EJB 3.0 annotation types generate interfaces required by the class as well as references to objects in the environment.
In many cases, defaults can be used instead of explicit metadata annotation elements. In these cases, a developer doesn't have to completely specify a metadata annotation to obtain the same result as if the annotation was fully specified. For example, by default, an entity bean (annotated by
@Entity) has a default entity type of
CMP, indicating that it has container-managed persistence. These defaults can make annotating enterprise beans very simple. In fact, in many cases, defaults are assumed when an annotation is not specified. In those cases, the defaults represent the most common specifications. For example, container-managed transaction demarcation (where the container, as opposed to the bean, manages the commitment or rollback of a unit of work to a database) is assumed for an enterprise bean if no annotation is specified. These defaults illustrate the "coding by exception" approach that guides EJB 3.0 technology. The intent is to simplify things for developers by forcing them to code things only where defaults are not adequate.
Business Interface
|
No component interface or home interface is required for a session bean. The one interface a session bean needs is a business interface. A business interface is a plain old Java interface ("POJI"). It does not have to extend or implement anything, and it does not throw
java.rmi.Remote Exception or
java.rmi.CreateException. A developer has the option of implementing the business interface or letting the interface be generated. In the previous example, a business interface named
Converter is generated for
ConverterBean by default. The generated interface is defined with the methods
dollarToYen and
yenToEuro. Generating business interfaces demonstrates that metadata annotation isn't the only place where defaults are assumed.
If a developer wanted to implement the business interface, the code would look like this:
@Stateless @Remote public class ConverterBean
implements Converter {
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 interface Converter {
BigDecimal dollarToYen(BigDecimal dollars);
BigDecimal yenToEuro(BigDecimal yen);
}
The Session Bean Client
Here's part of what a remote client (in this case, a session bean) for the EJB 3.0 technology version
ConverterBean might look like:
import converter.Converter;
import java.math.BigDecimal;
@Session public class ConverterClient {
@Inject Converter
Converter currencyConverter;
BigDecimal param = new BigDecimal("100.00");
BigDecimal amount = currencyConverter.dollarToYen(param);
...
}
|
Notice that there's no JNDI lookup code in the client. JNDI is no longer required to get references to resources and other objects in an enterprise bean's context. Instead, a developer can use resource and environment reference annotations in the bean class. These annotations are known as dependency annotations, because a developer uses them to declare the dependency of the enterprise bean on a resource or other object in the environment. The container then takes care of obtaining the references and providing them to the enterprise bean. Notice the @Inject annotation. It identifies a dependency injection, that is, it "injects" a dependency that the enterprise bean has on a resource or other object in the environment. Dependency injection can dramatically simplify what a developer has to code to obtain resource and environmental references.
Although JNDI lookup is not required, it can still be used if desired.
An Entity Bean With the EJB 3.0 Persistence Model
|
Now let's see what a developer does to develop an entity bean using EJB 3.0 technology. Once again, let's develop an entity bean named
PlayerBean that represents a player on a sports team.
PlayerBean the new lightweight persistence model, and has a many-to-many relationship to
TeamBean, which represents a sports team. This is the same entity bean presented in
Developing an Enterprise Bean with EJB 2.1 Technology. Here too you'll see how much easier it is to develop the bean using EJB 3.0 technology.
|
To create the entity bean, a developer only needs to code a bean class and annotate it with appropriate metadata annotations. Here's what part of the bean class looks like:
@Entity public class PlayerBean {
private Long playerId;
private String name;
private String position;
private double salary;
private Collection teams;
public PlayerBean() {}
@id(generate=AUTO) public Long getPlayerId() {
return id;
}
public String setPlayerId(Long id) {
this id=id;
}
public String getName() {
return name;
}
public String setName() {
this name= name;
}
...
public Collection getTeams() {
return teams;
}
public Collection setTeams(Collection teams) {
this teams=teams;
}
...
The
@Entity metadata annotation marks this as an entity bean. As is the case for a session bean in EJB 3.0 technology, there's no home interface, remote interface, or deployment descriptor to code. In fact, you don't even need a business interface for an entity bean. The bean class is coded as a POJO rather than a class that implements an interface such as
javax.ejb.EntityBean. This means, among other things, that instances of an entity bean can be created simply through a
new() operation. In addition, a developer no longer has to implement methods such as
ejbRemove,
ejbActivate,
ejbPassivate, or
ejbLoad in the bean class.
In EJB 3.0 technology, an entity bean class is a concrete class. It's no longer an abstract class. Notice that an entity bean, as illustrated by
PlayerBean, has non-abstract, private instance variables, such as
PlayerID. These variables represent the bean's persistent fields. The persistent fields are accessible through access methods such as
getPlayerID and
setPlayerID. Access methods were also used in this way in EJB 2.1 technology. However in EJB 3.0 technology, access methods are concrete, not abstract. In addition, these
get and
set methods can include logic, something that wasn't possible previously. This is useful for actions such as validating fields. Another improvement is that access to the persistence fields is not limited to the
get and
set methods. The persistence fields are also accessible through a bean class's business methods. One restriction however is that in EJB 3.0 technology, only methods within the class can access persistence fields -- in other words, you can't expose the instance variables outside of the class. That's why the instance variables are
private (they can also be
protected). Also notice the constructor in the code. Entity beans in EJB 3.0 technology must define a constructor with no parameters.
The
@Id metadata annotation is used for the primary key. The element-value pair
generate=AUTO tells the EJB container to pick a strategy for generating a primary key that's most appropriate for the database used by this application.
@Id is one of the annotation types for
object-relational mapping that's defined in the EJB 3.0 specification.
The Entity Bean Client
Here's what part of
RosterBean, the session bean that locally accesses
PlayerBean, might look like using EJB 3.0 technology:
@Stateless public class RosterBean {
@Inject EntityManager em;
public Long createPlayer(PlayerBean player) {
em.create(player);
return player.getPlayerID();
public PlayerBean findByPlayerId(Long playerId) {
return (PlayerBean) em.find("Player", playerID);
}
...
}
This version of the entity bean client is much smaller and easier to code than the version that uses EJB 2.1 technology. What makes the EJB 3.0 technology version smaller and simpler to code are two new features introduced in EJB 3.0 technology: dependency injection and the EntityManager.
Dependency Injection
As mentioned earlier, the
@Inject metadata type identifies a dependency injection -- it injects a dependency that the enterprise bean has on a resource or other object in the environment. The container then takes care of obtaining the reference to the resource or object and provides it to the enterprise bean. Dependency injection reflects a fundamental change in philosophy in the EJB architecture. Previous versions of the EJB architecture forced the developer into complying with the requirements of the EJB container in terms of providing classes and implementing interfaces. By comparison, dependency injection reflects the fact that the bean tells the EJB container what it needs, and then container satisfies those needs.
|
In the
RosterBean class the
@Inject annotation tells the EJB container that the session bean needs the
EntityManager object. In general, a dependency annotation identifies a needed resource or other object in the class's environment, and the name that is used to access that resource or object. The
@Inject annotation is used when all the needed elements of the annotation can be inferred. Two other annotations that can be used for dependency injection are
@Resource, which identifies a dependency on a resource, and
@EJB, which identifies a dependency on a session bean. A developer can specify a dependency injection on a setter method of a bean class or on an instance variable in a bean class.
It's the responsibility of the EJB container to inject the reference to the needed resource or object before the session bean is made available to handle a business method. This is typically at the time that the EJB container invokes the
setSessionContext method.
Dynamic Lookup
Dependency injection is not the only way to locate resources. The EJB 3.0 specification also includes a dynamic lookup capability. This capability is offered through a
lookup method in the
javax.ejb.EJBContext interface. This provides the ability to dynamically look up a needed resource. In the following example, the
setSessionContext subinterface of
EJBContext is injected into the client code and then used to dynamically look up a session bean named
myShoppingCart:
...
@Inject
private void setSessionContext(SessionContext ctx) {
this.ctx = ctx;
}
...
myShoppingCart = (ShoppingCart)ctx.lookup
("shoppingCart");
EntityManager
|
The persistence model in EJB 3.0 technology has been simplified for entity beans with container-managed persistence. A significant aspect of this simplified persistence model is the
EntityManager, a new API in the EJB 3.0 architecture. The
EntityManager is much like a home interface (but without a type). It is used to manage an entity bean's state. Various lifecycle methods are defined in the API, such as
create, which puts an entity bean in a managed state (that is, what's termed a "persistence context") and
remove, which removes an entity bean from the persistence context. When a bean instance is in a managed state, the data it represents is inserted into a database at the end of a transaction. The
EntityManager also provides a
flush method which synchronizes the persistence context with the underlying database. The data represented by a managed entity bean instance is inserted into a database when
flush is issued, that is, before the end of the transaction. What gets inserted at the end of a transaction or through
flush, and where in the database it gets inserted, depends on
Object/Relational mapping specifications -- another new feature in EJB 3.0. When a bean instance is removed through
remove, it's no longer in a managed state -- the data it represents is removed from the database at the end of a transaction, or sooner if
flush is specified.
Some other important methods provided by the
EntityManager include
find, which finds an entity bean instance by primary key, and
merge, which merges the state of a detached entity bean instance into the current persistent context. The entity bean model in the EJB 3.0 architecture allows for detaching entity beans instances. An entity bean instance becomes detached at the end of a transaction. Detached instances can exist outside of the transaction context in which they were created. This allows a developer to do things such as serialize the bean instance to another application tier. This is a significant improvement over older versions of the entity bean model. Previously, entity beans were not serializable. Developers had to resort to a
Data Transfer Object (DTO) to export an entity bean's state. In general, DTOs are not viewed positively. They're seen as "antipatterns," that is, bad solutions to a problem. The
merge method brings the state of a detached instance into the persistence context.
Looking back at the client code,
RosterBean first requests the EJB container to inject a reference to the
EntityManager:
@Inject EntityManager em;
Then
RosterBean invokes the EntityManager's
create method to put
PlayerBean in a managed state:
public Long createPlayer(PlayerBean player) {
em.create(player);
return player.getPlayerID();
It then invokes the EntityManager's
find method to find an instance of
PlayerBean by its primary key.
public PlayerBean findByPlayerId(Long playerId) {
| |
In addition to the simplifications highlighted in Developing an Enterprise Bean with EJB 3.0 Technology, the technology includes other ease-of-development improvements. Chief among these improvements are:
Let's briefly examine each of these improvements.
Support for Testing Outside of the EJB Container
It's now much easier to test entity beans outside of an EJB container. Previously, the entity bean component model, with its requirements for home and component interfaces, abstract entity bean classes, and virtual persistent fields, made it difficult to test entity beans outside of the container. The entity bean model in the EJB 3.0 architecture removes the requirement for these interfaces. The only thing required for an entity bean is a concrete bean class that has non-abstract persistent fields. In addition, an entity bean's lifecycle is controlled through the
EntityManager API, not through a home interface whose lifecycle methods are implemented by the EJB container. All of this makes entity beans less dependent on intervention by the EJB container, and so, can be more easily tested outside of the container.
|
The difficulty of testing outside a container was compounded in the past by container-managed relationships. Because a container automatically manages these relationships, there's no assurance that a bean tested outside of the EJB container will behave the same and produce the same results as inside the EJB container. In supporting the testing of enterprise beans outside of an EJB container, the EJB 3.0 architecture removes support for container-managed relationships -- but still maintains support for bean-managed relationships. Dropping support for container-managed relationships was a difficult decision to make by the Expert Group responsible for the architecture. But the need to support entity bean testing outside of the EJB container is a very important requirement -- one that needed to be met.
Support for Object/Relational Mapping
The EJB 3.0 specification defines metadata annotations for Object/Relational (O/R) mapping, that is, the mapping of an entity bean to the relational database structure that underlies it. Or put another way, O/R mapping is the mapping of an object schema to a database schema. An O/R mapping specification was not part of the EJB architecture in the past. By providing it, the EJB architecture gives developers a lot more control over the data model than in the past.
|
Significantly, O/R mapping support in the EJB 3.0 architecture supports inheritance and polymorphism. This means that a hierarchy of entity beans, where one entity bean subclasses another, can be mapped to a relational database structure, and the same queries or methods can be used against the different classes and subclasses in the hierarchy to produce results that are specific to that class or subclass.
Metadata annotations include those for table mappings, column mappings, and associations (such as many-to-one and one-to-many). For example, here is a metadata annotation that maps an entity bean named
Customer to a database table named
CUST:
@Entity
@Table(name="CUST")
public class Customer {...}
Here's an example that specifies a one-to-many relationship between the
Customer entity bean and an
Order entity bean:
@Entity
@Table(name="CUST")
public class Customer {
private Set<Order>orders = new Hashset();
...
@OneToMany(cascade=ALL)
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
...
The
cascade=ALL specification means that actions are cascaded through the relationship. So when a
Customer instance is created and its data is stored in the database, all the orders for that instance are also stored in the database.
Here's an example of mapping with inheritance. In the following example,
ValuedCustomer is an entity bean that extends the
Customer entity bean. The hierarchy is mapped to the
CUST table:
@Entity
@Table(name="CUST")
@Inheritance (strategy=SINGLE_TABLE,
discriminatorType=STRING,
discriminatorValue="CUST")
public class Customer {...}
@Entity
@Inheritance (discriminatorValue="VCUST")
public class ValuedCustomer extends Customer {...}
The
@Inheritance metadata annotation identifies an inheritance strategy for a class hierarchy. In this example, the strategy specified by the value of the
strategy element is
SINGLE_TABLE. This means that all the bean instances are mapped to a single table. This type of strategy requires a discriminator column in the table to identify one type of bean instance from another. The value in the column indicates the class for a specific bean instance. In the example,
discriminatorType specifies that the discriminator column contains strings. The
discriminatorValue element specifies that instances of
Customer are identified by the value
CUST, and instances of
ValuedCustomer are identified by the value
VCUST.
As mentioned earlier, defaults can be used in many places throughout the EJB architecture. Relying on defaults, the inheritance example can be as simple as this:
@Entity
public class Customer {...}
@Entity
public class ValuedCustomer extends Customer {...}
Enhanced EJB QL
EJB QL has been a very popular facet of EJB technology. However, despite its popularity, EJB QL has lacked some of the features of a full SQL query language, such as bulk update and delete operations, outer join operations, projection, and subqueries. EJB 3.0 technology adds those features to EJB QL.
|
For example, here's an EJB QL query that requests a bulk delete operation. It requests deletion of all customers whose status is inactive.
DELETE FROM Customer c WHERE c.status= 'inactive'
Here's an example of an EJB QL subquery. It's a correlated subquery that requests employees whose spouse is also an employee:
SELECT DISTINCT emp FROM EMPLOYEE emp WHERE EXISTS ( SELECT spouseEmp FROM EMPLOYEE spouseEmp WHERE spouseEmp = emp.spouse)
Another important addition to EJB QL is support for outer join-based prefetching (termed a "FETCH JOIN"). A FETCH JOIN for a query returns a result and the prefetched data. For this to work, there must be an association between the entity that the query returns as a result and the prefeteched data. This gives developers a way of bringing in data that an application immediately needs, and related data that it will later need. Prefetching can improve application performance by reducing the number of EJB QL queries that need to be invoked (and the subsequent database transmissions that need to be made). For example, here is an outer join with a fetch that requests all customers in California and all the orders for those customers:
SELECT DISTINCT c FROM CUSTOMER c LEFT JOIN FETCH c.orders WHERE c.address.state = 'CA'
Beyond EJB QL enhancements, the EJB 3.0 architecture also adds a Query API to create dynamic queries and named queries (named queries are similar to static queries in EJB-SQL). Here's an example that creates a dynamic query. The query produces a result set of customers whose name is like a name that's dynamically supplied to the query.
@Inject public EntityManager em;
public List findWithName (string Name) {
return em.CreateQuery {
"SELECT c FROM Customer c" +
"WHERE c.name LIKE :custName")
.setParameter("custName", name)
.setMaxResults(10)
.listResults();
}
...
The
CreateQuery method is a factory method of the
EntityManager for creating queries. This example uses the Query API on the entity manager to generate the dynamic query. Like dynamic queries in SQL, the query is specified as a string. Notice the named parameter
:custName. Named parameters have a colon (:) prefix. This is another enhancement to EJB QL. Named parameters can be used in addition to positional parameters in EJB QL queries. There are three method calls in the example. The
setParameter method dynamically binds a specific name to the query at run time. The
setMaxResults and
listResults methods limit the result set to 10, and list the results, respectively.
Here's an example that creates a named query. The named query in the example does essentially the same thing as the dynamic query in the previous example, that is, find customers who have a specific name. To create a named query, a developer first uses a metadata annotation (
@NamedQuery) to define the named query:
@NamedQuery {
name="findAllCustomersWithName",
queryString=
"SELECT c" +
"FROM Customer c" +
"WHERE c.name LIKE :custName"
}
Next, the developer creates the named query that was previously defined:
@Inject public EntityManager em;
customers = em.createNamedQuery("findAllCustomersWithName")
.setParameter("custName", "Smith")
.setMaxResults(10)
.listResults();
}
...
| |
This article only highlights some of the simplifications made to EJB technology in the EJB 3.0 Specification. You'll find even more improvements to the technology, and more detailed information, by reviewing the specification. Your feedback on the EJB 3.0 Specification is also very important in ensuring that EJB technology meets its objectives of simplifying the developer's experience while maintaining its power and sophistication.
| |
| |
Ed Ort is a staff member of java.sun.com and developers.sun.com. He has written extensively about relational database technology, programming languages, and web services.
1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.