In the following example we will develop a simple CMP 2.0 entity bean—the Customer EJB. The Customer EJB models the concept of a cruise customer or passenger, but its design and use are applicable across many commercial domains.
As the chapter progresses, the Customer EJB will be expanded and its complexity will increase to illustrate concepts discussed in each section. This section serves only to introduce you to the entity bean and some basic concepts regarding its development, packaging, and deployment. To simplify things, we will skim over some concepts that are discussed in detail later in the chapter.
Although CMP 2.0 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 will be used by the container tool for
generating a concrete implementation, the persistence entity class,
which will run in the EJB container. The mechanism used by the
container tool 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 and relationship field defined in the abstract persistence schema of the deployment descriptor. The container tool 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 to developing entity beans.
Here is a very simple definition of the
CustomerBean
class that is developed and packaged
for deployment by the bean developer:
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 defined as an abstract class. This is required by CMP 2.0 to reinforce the idea that the CustomerBean is not deployed directly into the container system. Since abstract classes cannot be instantiated, in order to be deployed the bean class must be subclassed by a persistence class generated by the deployment tool. Also, the accessor methods are themselves declared as abstract, which necessitates that the container tool implement them.
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 11.
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 the ejbCreate() method 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, including that of a wrapper for primitive types and custom-defined classes. 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. 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 Why ejbCreate( ) Returns Null for an explanation of ejbCreate()’s return type.
The ejbPostCreate() method is used to perform initialization after the entity bean is created but before it services any requests from the client. This method usually is used to perform work on the entity bean’s relationship fields, which can occur only after the bean’s ejbCreate() method is invoked and it’s 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() in more detail 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. These methods are defined as abstract without method bodies. As was already mentioned, when the bean is processed by a container tool, 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 will 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 clients will 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 the signatures
of 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. When the remote interface
methods match the persistence field methods, the client has direct
access to the entity bean’s persistence fields.
You are not required to match abstract accessor methods in the bean class with methods in the remote interface. In fact, it’s recommended that the remote interface be as independent of the abstract programming model as possible.
While remote methods can match persistence fields in the bean class,
the specification prohibits the remote methods from matching
relationship fields, which 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 you learned 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.[26] 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.
The following code contains 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 you 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:
public interface CustomerHomeRemote extends javax.ejb.EJBHome { public CustomerRemotecreateWithSSN
(Integer id, String socialSecurityNumber) throws CreateException, RemoteException; public CustomerRemotecreateWithTIN
(Integer id, String taxIdentificationNumber) throws CreateException, RemoteException; public CustomerRemote findByPrimaryKey(Integer id) throws FinderException, RemoteException; }
Suffixes are useful when you need
create()
methods to be more descriptive or need to
further qualify them for method overloading. 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 experienced 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, but no matching method needs
to be defined in the entity bean class. The implementation of
findByPrimaryKey()
is generated automatically by
the deployment tool. At runtime, the
findByPrimaryKey()
method will automatically
locate and return 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 finder methods are automatically implemented by
the deployment tool based on the method signature and an EJB QL
statement, which is similar to SQL but is specific to EJB. Custom
finder methods and EJB QL are discussed in detail in Chapter 8.
CMP 2.0 entity beans must be packaged for deployment with an XML deployment descriptor that describes the bean and its abstract persistence schema. In most cases, the bean developer is not directly exposed to the XML deployment descriptor but rather will use the container’s visual deployment tools to package the beans. In this book, however, I will describe the declarations of the deployment descriptor in detail so you have a full understanding of their content and organization.
The XML deployment descriptor, for our simple Customer EJB, contains many elements that should be familiar to you from Chapter 4. The elements specific to entity beans and persistence are most important to us in this chapter. The following is the complete XML deployment descriptor for the Customer EJB:
<!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> <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 first few elements, which declare the Customer EJB name,
(CustomerEJB
) as well as its home, remote, and
bean class, should already be familiar to you from Chapter 4. The
<security-identity>
element was covered in
Chapter 3.
The <assembly-descriptor>
elements, which
declare the security and transaction attributes of the bean, were
also covered briefly in Chapter 4. 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 their
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 tells the container system which version of container-managed
persistence is being used. EJB 2.0 containers must support the new
container-managed persistence model as well as the old one defined in
EJB 1.1. The value of the <cmp-version>
element can be either 2.x
or
1.x
, for the EJB 2.0 and 1.1 versions,
respectively. The <cmp-version>
element is
optional. 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. Reentrant behavior was covered in Chapter 3.
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—the
get
and set
parts of the method
names are ignored when matching methods to
<field-name>
declarations.
Table 6-1. Field names for abstract accessor methods
CMP field |
Abstract accessor method |
---|---|
|
|
|
|
|
|
CMP 2.0 requires that the
<field-name>
values start with a lowercase letter while
their matching accessor methods take the form
get<field-name
value>()
,
set<field-name
value>()
,
with the first letter of the <field-name>
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 2.0, that field
names with multiple words are declared using “camel
case,” where 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 only one field of the entity bean’s
container-managed fields describes a unique identifier for the bean.
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 11.
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 sold and/or deployed in an EJB container. The examples available from http://www.oreilly.com/catalog/entjbeans3/ contain a properly prepared JAR file that includes the Customer EJB’s interfaces, bean class, and deployment descriptor. You may use these files or develop them yourself. 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
Most EJB servers provide graphical or command-line 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. If you prefer to use these tools, the workbooks will step you through the process of deploying an entity bean using specific vendors’ container- deployment tools.
Once the CustomerEJB
is packaged in a JAR file, it’s
ready to be processed by the deployment tools. For most vendors,
these tools are combined into one graphical user interface used at
deployment time. 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). These tasks are also accomplished using the
deployment tools provided by your vendor. The
workbooks provide step-by-step
instructions for deploying the CustomerEJB
in
specific vendor environments.
The Client
application is a remote
client to the
CustomerEJB
that will create several customers,
find them, and then remove 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(args [ 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(args [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.
Please refer to Workbook Exercise 6.1, Basic Persistence in CMP 2.0. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.