The Customer EJB is a simple CMP entity bean that models the concept of a cruise customer or passenger, but its design and use are applicable across many commercial domains. This section introduces the Customer bean’s development, packaging, and deployment. We greatly expand the bean’s features as we progress through the chapter.
Although CMP is database-independent, the examples throughout this
book assume that you are using a relational database. This means that we
will need a CUSTOMER
table from which to get our
customer data. The relational database table definition in SQL is as
follows:
CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY NOT NULL, LAST_NAME CHAR(20), FIRST_NAME CHAR(20) )
The
CustomerBean
class is an abstract class that the
container uses for generating a concrete implementation, the
persistence entity class. The mechanism used by the container for
generating a persistence entity class varies, but most vendors
generate a subclass of the abstract class provided by the bean
developer (see Figure 6-4).
The bean class must declare accessor (set and get) methods for each
persistence field and relationship field defined in the deployment
descriptor. The container needs both the abstract accessor methods
(defined in the entity bean class) and the XML elements of the
deployment descriptor to fully describe the bean’s
persistence schema. In this book, the entity bean class is always
defined before the XML elements, because it’s a more
natural approach for most Java developers. Here is a very simple
definition of the CustomerBean
class:
package com.titan.customer; import javax.ejb.EntityContext; public abstract class CustomerBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id){ setId(id); return null; } public void ejbPostCreate(Integer id){ } // abstract accessor methods public abstract Integer getId( ); public abstract void setId(Integer id); public abstract String getLastName( ); public abstract void setLastName(String lname); public abstract String getFirstName( ); public abstract void setFirstName(String fname); // standard callback methods public void setEntityContext(EntityContext ec){} public void unsetEntityContext( ){} public void ejbLoad( ){} public void ejbStore( ){} public void ejbActivate( ){} public void ejbPassivate( ){} public void ejbRemove( ){} }
The CustomerBean
class is required to be abstract
in order to reinforce the idea that the
CustomerBean
is not deployed directly. Since
abstract classes cannot be instantiated, this class must be
subclassed by a persistence class generated by the deployment tool.
When generating the persistence class, the deployment tool must
generate the accessor methods, which are themselves declared as
abstract.
The CustomerBean
extends the
javax.ejb.EntityBean
interface, which defines several callback
methods, including setEntityContext( )
,
unsetEntityContext( )
, ejbLoad( )
, ejbStore( )
, ejbActivate( )
, ejbPassivate( )
, and
ejbRemove( )
. These methods are important for
notifying the bean instance about events in its life cycle, but we do
not need to worry about them yet. We will discuss these methods in
detail in Chapter 10.
The first method in the entity bean class is ejbCreate( )
, which takes a reference to an Integer
object as its only argument. The ejbCreate( )
method is called when the remote client invokes the create( )
method on the entity bean’s home
interface. This concept should be familiar, since
it’s the same way ejbCreate( )
worked in the Cabin bean developed in Chapter 4. The ejbCreate( )
method
is responsible for initializing any persistence fields before the
entity bean is created. In this first example, the
ejbCreate( )
method is used to initialize the
id
persistence field, which is represented by the
setId( )
/getId( )
accessor
methods.
The return type of ejbCreate( )
is an
Integer
, which is the primary
key of the entity bean. The primary key is a unique
identifier that can take a variety of forms. In this case, the
primary key (the Integer
) is mapped to the
ID
field in the CUSTOMER
table.
This will become evident when we define the XML deployment
descriptor. However, although the return type of the
ejbCreate( )
method is the primary key, the value
actually returned by the ejbCreate( )
method is
null
. The EJB container and persistence class will
extract the primary key from the bean when it is needed. See the
sidebar “Why ejbCreate( ) Returns
Null” for an explanation of ejbCreate( )
’s return type.
The ejbPostCreate( )
method performs initialization after the
entity bean is created but before it services any requests from the
client. Usually, this method is used to perform work on the entity
bean’s relationship fields, which can occur only
after the bean’s ejbCreate( )
method has been invoked and added to the database. For each
ejbCreate( )
method, there must be a matching
ejbPostCreate( )
method that has the same method
name and arguments but returns void. This pairing of
ejbCreate( )
and ejbPostCreate( )
ensures that the container calls the correct methods
together. We’ll explore the use of the
ejbPostCreate( )
later; for now,
it’s not needed, so its implementation is left
empty.
The abstract accessor methods (setLastName( )
,
getLastName( )
, setFirstName( )
, getFirstName( )
) represent the
persistence fields in the CustomerBean
class. When
the bean is processed by a container, these methods will be
implemented by a persistence class based on the abstract persistence
schema (XML deployment descriptor elements), the particular EJB
container, and the database used. Basically, these methods fetch and
update values in the database and are not implemented by the bean
developer.
We need a CustomerRemote
interface for the
Customer EJB, because the bean will be accessed by clients outside
the container system. The remote interface defines the business
methods that clients use to interact with the entity bean. The remote
interface should define methods that model the public aspects of the
business concept being modeled; that is, those behaviors and data
that should be exposed to client applications. Here is the remote
interface for CustomerRemote
:
package com.titan.customer; import java.rmi.RemoteException; public interface CustomerRemote extends javax.ejb.EJBObject { public String getLastName( ) throws RemoteException; public void setLastName(String lname) throws RemoteException; public String getFirstName( ) throws RemoteException; public void setFirstName(String fname) throws RemoteException; }
Any methods defined in the remote interface must match methods
defined in the bean class. In this case, the accessor methods in the
CustomerRemote
interface match persistence field
accessor methods in the CustomerBean
class—with a few exceptions, methods in the remote interface
can match any business method in the bean class
While remote methods can match persistence fields and other business
methods in the bean class, the specification prohibits the remote
methods from matching callback methods (ejbRemove( )
, ejbActivate( )
, ejbLoad( )
, etc.) or relationship fields—relationship fields
are used to access other entity beans. In addition, remote methods
may not modify any container-managed persistence fields that are part
of the primary key of an entity bean. Notice that the remote
interface does not define a setId( )
method, which
would allow it to modify the primary key.
The remote home interface of
any entity bean is used to create, locate, and remove entities from
the EJB container. Each entity bean type may have its own remote home
interface, local home interface, or both. As explained in Chapter 5, the remote and local home interfaces
perform essentially the same function. The home interfaces define
three basic kinds of methods: home business methods, zero or more
create methods, and one or more find
methods. The create( )
methods act like remote
constructors and define how new entity beans are created. In our
remote home interface, we provide only a single create( )
method, which matches the corresponding
ejbCreate( )
method in the bean class. The find
method is used to locate a specific Customer EJB using the primary
key as a unique identifier.
Here is the complete definition of the
CustomerHomeRemote
interface:
package com.titan.customer; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface CustomerHomeRemote extends javax.ejb.EJBHome { public CustomerRemote create(Integer id) throws CreateException, RemoteException; public CustomerRemote findByPrimaryKey(Integer id) throws FinderException, RemoteException; }
A create( )
method may be suffixed with a name in
order to further qualify it when overloading method arguments. This
is useful if we have two different create( )
methods that take arguments of the same type. For example, we could
declare two create( )
methods for Customer that
both declare an Integer
and a
String
argument. The String
argument might be a Social Security number (SSN) in one case and a
tax identification number (TIN) in another—individuals have
Social Security numbers while corporations have tax identification
numbers. Here’s how these methods might look:
public interface CustomerHomeRemote extends javax.ejb.EJBHome { public CustomerRemote createWithSSN(Integer id, String socialSecurityNumber) throws CreateException, RemoteException; public CustomerRemote createWithTIN(Integer id, String taxIdentificationNumber) throws CreateException, RemoteException; public CustomerRemote findByPrimaryKey(Integer id) throws FinderException, RemoteException; }
Each create<SUFFIX>( )
method must have a corresponding
ejbCreate<SUFFIX>( )
in the bean class. For
example, the CustomerBean
class needs to define
ejbCreateWithSSN( )
and ejbCreateWithTIN( )
methods as well as matching
ejbPostCreateWithSSN( )
and
ejbPostCreateWithTIN( )
methods. We are keeping
this example simple, so we need only one create( )
method and, therefore, no suffix.
Enterprise JavaBeans specifies that create( )
methods in the remote home interface must throw the
javax.ejb.CreateException
. In the case of
container-managed persistence, the container needs a common exception
for communicating problems that may occur during the create process.
Entity remote home interfaces must define a
findByPrimaryKey( )
method that takes the entity
bean’s primary key type as its only argument. No
matching method needs to be defined in the entity bean class. The
implementation of findByPrimaryKey( )
is generated
automatically. At runtime, the findByPrimaryKey( )
method automatically locates and returns a remote reference to the
entity bean with the matching primary key.
The bean developer can also declare other find methods. For example,
the CustomerHomeRemote
interface could define a
findByLastName(String lname)
method that locates
all the Customer entities with the specified last name. These types
of find methods are automatically implemented by the deployment tool
based on the method signature and an EJB QL statement. EJB QL is
similar to SQL but is specific to EJB. Custom finder methods and EJB
QL are discussed in detail in Chapter 8.
CMP entity beans must be packaged with an XML deployment descriptor that describes the bean and its abstract persistence schema. With many commercial containers, the bean developer is not directly exposed to the deployment descriptor, but instead uses the container’s deployment tools to package beans. In this book, however, I describe the deployment descriptor in detail so you have a full understanding of its content and organization.
Here is the complete XML deployment descriptor for the Customer EJB in EJB 2.1. Many of the elements in this descriptor should be familiar from Chapter 4; we will focus on the new elements:
<?xml version="1.0" encoding="UTF-8" ?> <ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1"> <enterprise-beans> <entity> <ejb-name>CustomerEJB</ejb-name> <home>com.titan.customer.CustomerHomeRemote</home> <remote>com.titan.customer.CustomerRemote</remote> <ejb-class>com.titan.customer.CustomerBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>Customer</abstract-schema-name> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <primkey-field>id</primkey-field> <security-identity><use-caller-identity/></security-identity> </entity> </enterprise-beans> <assembly-descriptor> <security-role> <role-name>Employees</role-name> </security-role> <method-permission> <role-name>Employees</role-name> <method> <ejb-name>CustomerEJB</ejb-name> <method-name>*</method-name> </method> </method-permission> <container-transaction> <method> <ejb-name>CustomerEJB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> <container-transaction> </assembly-descriptor> </ejb-jar>
The deployment descriptor for EJB 2.0 is exactly the same, except
that it uses XML DTD instead of XML Schema, so the first tag in the
EJB 2.0 deployment descriptor is the document declaration followed by
the <ejb-jar>
element.
<?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> ... </ejb-jar>
The first few elements in the Customer EJB’s
deployment descriptor should be familiar; they declare the Customer
EJB name, (CustomerEJB
) as well as its home,
remote, and bean class. The
<security-identity>
element should also be familiar, as
well as the
<assembly-descriptor>
elements, which declare the security
and transaction attributes of the bean. In this case, they state that
all employees can access any CustomerEJB
method
and that all methods use the Required
transaction
attribute.
Container-managed persistence entities also need to declare a
persistence type, version, and whether they are reentrant. These
elements are declared under the <entity>
element.
The <persistence-type>
element tells the container system
whether the bean will be a container-managed persistence entity or a
bean-managed persistence entity. In this case it’s
container-managed, so we use Container
. Had it
been bean-managed, the value would have been Bean
.
The <cmp-version>
element is optional; it tells the
container system which version of container-managed persistence is
being used. The value of the <cmp-version>
element can be either 2.x
or
1.x
. The 2.x
designator is used
for EJB 2.1 and 2.0, while 1.x
is used for EJB
1.1. EJB 2.1 and 2.0 containers are required to support EJB 1.1 CMP
for backward compatibility. If it is not declared, the default value
is 2.x
. It’s not really needed
here, but it’s specified as an aid to other
developers who might read the deployment descriptor.
The <reentrant>
element indicates whether reentrant
behavior is allowed. In this case the value is
False
, which indicates that the
CustomerEJB
is not reentrant (i.e., loopbacks are
not allowed). A value of True
would indicate that
the CustomerEJB
is reentrant and that loopbacks
are permitted.
The entity bean must also declare its container-managed persistence fields and its primary key:
<entity> <ejb-name>CustomerEJB</ejb-name> <home>com.titan.customer.CustomerHomeRemote</home> <remote>com.titan.customer.CustomerRemote</remote> <ejb-class>com.titan.customer.CustomerBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <primkey-field>id</primkey-field> </entity>
The container-managed persistence fields are the
id
, lastName
, and
firstName
, as indicated by the
<cmp-field>
elements. The
<cmp-field>
elements must have matching
accessor methods in the CustomerBean
class. As you
can see in Table 6-1, the values declared in the
<field-name>
element match the names of
abstract accessor methods we declared in the
CustomerBean
class.
Table 6-1. Field names for abstract accessor methods
CMP requires that the <field-name>
values
start with a lowercase letter. The names of the matching accessor
methods take the form get<field-name>( )
,
set<field-name
>( )
(the
first letter of the field name is capitalized). The return type of
the get
method and the parameter of the
set
method determine the type of the
<cmp-field>
. It’s the
convention of this book, but not a requirement of CMP, that field
names with multiple words are declared using “camel
case,” in which each new word starts with a capital
letter (e.g., lastName
).
Finally, we declare the
primary key using two fields,
<prim-key-class>
and
<primkey-field>
.
<prim-key-class>
indicates the type of the primary key,
and <primkey-field>
indicates which of the
<cmp-field>
elements designates the primary
key. The Customer EJB uses a
single-field
primary
key, in which the bean’s identifier is composed of a
single container-managed field. The
<primkey-field>
must be declared if the
entity bean uses a single-field primary key.
Compound
primary
keys, which use more than one of the persistence fields as a key, are
often used instead. In this case, the bean developer creates a custom
primary key. The <prim-key-class>
element is
always required, whether it’s a single-field,
compound, or unknown primary key. Unknown keys use a field that may
not be declared in the bean at all. The different types of primary
keys are covered in more detail in Chapter 10.
Now that you have created the interfaces, bean class, and deployment descriptor, you’re ready to package the bean for deployment. As you learned in Chapter 4, the JAR file provides a way to “shrink-wrap” a component so it can be deployed in an EJB container. The command for creating a new EJB JAR file is:
dev % jar cf customer.jar com/titan/customer/*.class com/titan/customer/META-INF/ejb-jar.xml F:..dev>jar cf cabin.jar com itancustomer*.class com itancustomer META-INFejb-jar.xml
There are a number of tools that create the XML deployment descriptor and package the enterprise bean into a JAR file automatically. Some of these tools even create the home and remote interfaces automatically, based on input from the developer.
Once the CustomerEJB
is packaged in a JAR file,
it’s ready to be deployed in an EJB container. The
point is to map the container-managed persistence fields of the bean
to fields or data objects in the database. (Earlier in this chapter,
Figure 6-2 and Figure 6-3 showed
two visual tools used to map the Customer EJB’s
persistence fields.) In addition, the
security roles need to be
mapped to the subjects in the security realm of the target
environment and the bean needs to be added to the naming service and
given a JNDI lookup name (name binding).
The Client
application is a remote
client to the
CustomerEJB
that creates several customers, finds
them, and then removes them. Here is the complete definition of the
Client
application:
import javax.naming.InitialContext; import javax.naming.Context; import javax.naming.NamingException; import java.util.Properties; public class Client { public static void main(String [] args)) throws Exception { //obtain CustomerHome Context jndiContext = getInitialContext( ); Object obj=jndiContext.lookup("CustomerHomeRemote"); CustomerHomeRemote home = (CustomerHomeRemote) javax.rmi.PortableRemoteObject.narrow(obj,CustomerHomeRemote.class); //create Customers for(int i =0;i <args.length;i++){ Integer primaryKey =new Integer(i); String firstName = args [i ]; String lastName = args [i ]; CustomerRemote customer = home.create(primaryKey); customer.setFirstName(firstName); customer.setLastName(lastName); } //find and remove Customers for(int i = 0;i < args.length;i++){ Integer primaryKey = new Integer(i); CustomerRemote customer = home.findByPrimaryKey(primaryKey); String lastName = customer.getLastName( ); String firstName = customer.getFirstName( ); System.out.print(primaryKey+"="); System.out.println(firstName+""+lastName); //remove Customer customer.remove( ); } } public static Context getInitialContext( throws javax.naming.NamingException { Properties p =new Properties( ); //...Specify the JNDI properties specific to the vendor. return new javax.naming.InitialContext(p); } }
The Client
application creates several Customer
EJBs, sets their first and last names, prints out the persistence
field values, and then removes the entities from the container system
and, effectively, the database. To deploy the examples in this
section, see Exercise 6.1 in the Workbook.