Chapter 4. Developing Your First Beans
The primary goal of the EJB 3.0 and Java Persistence specifications was to make it as easy as possible to write and deploy an EJB-based application. Creating an application is as easy as compiling your code, jarring up your classes, and running your application server. This chapter gives an introduction to writing your first entity and session bean. You’ll find that getting up and running is fairly simple.
Developing an Entity Bean
Let’s start by examining how to create an entity bean. We’ll implement the Cabin entity that is part of the Titan Cruises Java EE application. The Cabin entity encapsulates the data and behavior associated with a cruise ship cabin in Titan’s business domain. Although you can interact with entity beans outside of an application server, we will later create a TravelAgent session bean to serve as a data access interface for creating and locating cabins.
Cabin: The Bean Class
When developing an entity bean, all we need to define is the bean class. Entities in Java Persistence are plain Java objects that are annotated with O/R mapping metadata. We started to define the Cabin entity in Chapter 2; here, we add two new methods for setting and getting the ship ID and the bed count. The ship ID identifies the ship to which the cabin belongs, and the bed count tells how many people the cabin can accommodate:
package com.titan.domain import javax.persistence.*; @Entity @Table(name="CABIN") public class Cabin implements java.io.Serializable{ private int id; private String name; private int deckLevel; private int shipId; private int bedCount; @Id @Column(name="ID") public int getId( ) { return id; } public void setId(int pk) { id = pk; } @Column(name="NAME") public String getName( ) { return name; } public void setName(String str) {name = str; } @Column(name="DECK_LEVEL") public int getDeckLevel( ) { return deckLevel; } public void setDeckLevel(int level) { deckLevel = level; } @Column(name="SHIP_ID") public int getShipId( ) { return shipId; } public void setShipId(int sid) { shipId = sid; } @Column(name="BED_COUNT") public int getBedCount( ) { return bedCount; } public void setBedCount(int bed) { bedCount = bed; } }
The Cabin
bean class is
annotated with @javax.persistence.Entity
and @javax.persistence.Table
. The @Entity
annotation tells the persistence provider
that this is an entity class that is mapped to a database and that
can be managed by an EntityManager
service. The @Table
annotation tells the EJB container to
which database table the bean class should map. The bean class
implements java.io.Serializable
, but it is not required to. Having entity
classes implement Serializable
allows them to be used as the parameters and return values of the
remote interface methods of a session bean. This allows you to use
the same class for both persistence and data transfer.
Cabin
also defines four
properties: name, deckLevel,
shipId
, and bedCount
.
Properties are attributes of an entity bean
that can be accessed by public set and
get methods; they can also be accessed directly
through the bean’s fields. In this example, we use public
set and get methods. For
each property, we define how it maps to the columns in the CABIN
database table with the @javax.persistence.Column
annotation. The getId( )
property is marked as the primary
key of the Cabin entity by using the @javax.persistence.Id
annotation. The primary key denotes the
identity of a particular entity bean at runtime and when it is
persisted in the database.
Tip
Table-mapping annotations like @Table
and @Column
are not required; if they’re omitted,
they default to the unqualified name of the class and the property
name, respectively. However, primary-key identification is
required.
Notice that we have made the Cabin
bean class a part of a new package
named com.titan .domain.
Place all the classes and
interfaces associated with each type of bean in a package specific
to the bean. Because our beans are for use by Titan Cruises, we
placed these packages in the com.titan
package hierarchy. We also
created directory structures that match package structures. If you
are using an IDE that works directly with Java files, create a new
directory called dev (for development) and
create the directory structure shown in Figure 4-1. Copy the
Cabin
bean class into your IDE
and save its definition to the domain
directory. Compile the Cabin
bean
class to ensure that its definition is correct. The
Cabin.class file, generated by the IDE’s
compiler, should be written to the domain
directory, the same directory as the Cabin.java
file. The rest of the Cabin bean’s classes will be placed in this
same directory. We are done defining the Cabin entity.
The persistence.xml File
The Java Persistence specification requires a simple XML
deployment descriptor file, persistence.xml,
which configures basic things like the name of the EntityManager
service that will be
managing the set of entities deployed in a particular deployment
package. It also defines what database the EntityManager
service will be
communicating with and may also specify additional, vendor-specific
properties.
<persistence> <persistence-unit name="titan"> <jta-data-source>java:/TitanDB</jta-data-source> </persistence-unit> </persistence
The <
name
>
element represents the set of classes
that are managed by a given EntityManager
. The <jta-data-source>
element defines
the database that will be used to persist entities in this
deployment. This persistence.xml file is
located in a META-INF directory.
persistence.xml is discussed in detail in Chapter 5.
Developing a Session Bean
Now that our Cabin entity is ready, we need to create a session bean that will act as the interface for interacting with the Cabin entity. This session bean will take on the business process and responsibilities of a travel agent and will be called the TravelAgent EJB.
TravelAgentRemote: The Remote Interface
The first part of creating the TravelAgent EJB is to define
its remote interface. This interface specifies what business methods
a client is allowed to invoke on the EJB. Clients interact with the
TravelAgent’s createCabin( )
and
findCabin( )
methods to
manipulate Cabin entities:
package com.titan.travelagent; import javax.ejb.Remote; import com.titan.domain.Cabin; @Remote public interface TravelAgentRemote { public void createCabin(Cabin cabin); public Cabin findCabin(int id); }
There really is nothing special about the remote interface of
the TravelAgent EJB. It looks like a plain Java interface, except
that it is annotated with the @javax.ejb.Remote
annotation. This annotation tells the EJB
container that this particular interface is the remote business
interface of the TravelAgent EJB. Unlike EJB 2.1, also notice that
the business methods do not have to throw java.rmi.RemoteException
. They can if they want to, but they don’t
have to.
TravelAgentBean: The Bean Class
Now that we have defined the remote interface to the
TravelAgent EJB, we need to implement the bean class that contains the business logic for this session
bean. The TravelAgent EJB is defined as a stateless bean. We use the
@javax.ejb.Stateless
annotation to denote this. Although they
are not required to, it is good practice for stateless session beans
to implement all of their business interfaces so that the
client/bean contract can be enforced by the Java compiler. In this
case, the business interface is TravelAgentRemote
. Here is the complete definition of the TravelAgentBean
class:
package com.titan.travelagent; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import com.titan.domain.Cabin; @Stateless public class TravelAgentBean implements TravelAgentRemote{ @PersistenceContext (unitName="titan") private EntityManager manager; public void createCabin(Cabin cabin) { manager.persist(cabin); } public Cabin findCabin(int pKey) { return manager.find(Cabin.class, pKey); } }
A bunch of things are going on in this implementation. First,
the TravelAgentBean
class uses
the @javax.persistence.PersistenceContext
annotation to get access to the EntityManager
service that it uses to
create and find Cabin entities. @Persistence-Context
tells the EJB
container that it must set the manager
field with an EntityManager
instance. The EJB container
sees that the type of the field is javax.persistence.EntityManager
and knows
that it must set the field to be a reference to an EntityManager
service that references the
Titan persistence unit we defined in the
persistence.xml file. This field will be
initialized when the bean instance is instantiated.
The next two methods interact with the EntityManager
service to create a Cabin
entity within the database and to locate a Cabin entity based on its
primary key. The createCabin( )
method invokes EntityManager.persist(
)
, passing in an instance of the entity that we want to
create. After this operation is complete, the Cabin
instance is attached to persistence
management and is stored in the database. We’ll see later on that
you create the Cabin
instance as
you would any other plain Java object. The findCabin( )
method takes as its parameter
a primary key of a cabin in the database. It calls the EntityManager.find( )
method, passing the
Cabin
bean class as a parameter
and the actual primary key. The Cabin
bean class parameter tells the
Entity-Manager
which entity it is
trying to find in the database. The findCabin( )
method then returns the found
Cabin entity back to the remote client.
titan.jar: The JAR File
The JAR file is a platform-independent file format for compressing, packaging, and delivering several files together. Based on the Zip file format and the zlib compression standards, the JAR (Java Archive) tool was originally developed to make downloads of Java applets more efficient. As a packaging mechanism, however, the JAR file format is a very convenient way to “shrink-wrap” components and other software for delivery to third parties. In EJB development, a JAR file packages all the classes and interfaces associated with a bean. Besides EJB definitions and classes, you are also allowed to package entity beans and their persistence.xml deployment descriptor.
Creating the JAR file for deployment is easy. Position yourself in the dev directory that is just above the com/titan directory tree and execute the following command:
C:\dev> jar cf titan.jar com/titan/domain/*.class com/titan/travelagent/*.class META-INF/persistence.xml
The c
option tells the
jar utility to create a new JAR file that
contains the files indicated in subsequent parameters. It also tells
the jar utility to stream the resulting JAR
file to standard output. The f
option tells jar to redirect the standard
output to a new file named in the second parameter
(titan.jar). It’s important to get the order of
the option letters and the command-line parameters to match. You can
learn more about the jar utility and the
java.util.zip
package in Java in a
Nutshell or Learning Java (both
published by O’Reilly).
The jar utility creates the file
titan.jar in the dev
directory. If you’re interested in looking at the contents of the
JAR file, you can use any standard Zip application (WinZip, PKZIP,
etc.) or the command jar tvf
titan.jar
. Figure 4-2 shows the
structure of this Jar file.
Creating a CABIN Table in the Database
One of the primary jobs of a deployment tool is mapping entity
beans to databases. In the case of the Cabin entity, we must map its
id, name, deckLevel, shipId
, and
bedCount
fields to some data
source. Before proceeding with deployment, you need to set up a
database and create a CABIN
table. You can use the following standard SQL statement to create a
CABIN
table that will be
consistent with the examples provided in this chapter:
create table CABIN ( ID int primary key NOT NULL, SHIP_ID int, BED_COUNT int, NAME char(30), DECK_LEVEL int )
This statement creates a CABIN
table that has five columns
corresponding to the container-managed fields in the Cabin
class. Once the table is created and
connectivity to the database is confirmed, you can proceed with the
deployment process.
Alternatively, most if not all persistence provider implementations will support autogeneration of your database tables. For example, you can configure JBoss to create the tables for each entity bean when the application server boots and deploys its EJBs. Other application servers will have different mechanisms to do this.
Deploying the EJB JAR
Deployment is the process of reading the bean’s JAR file, changing or adding properties to the deployment descriptor, mapping the bean to the database, defining access control in the security domain, and generating any vendor-specific classes needed to support the bean in the EJB environment. Some EJB server products require you to use a set of deployment tools to deploy your EJBs to your application server. These tools may provide a graphical user interface or a set of command-line programs.
A deployment tool reads the JAR file and looks for annotated classes as well as any possible XML deployment descriptors so that it can determine which EJBs and entities are being deployed. In a graphical deployment wizard, the metadata of each EJB and entity is presented using a set of property sheets similar to those used in environments such as Visual Basic .NET, PowerBuilder, and JBuilder.
Some EJB servers, like JBoss, do not require any special vendor-generated classes, so there is no need for a deployment tool. JBoss, for instance, just requires you to put your ejb JAR in a deploy/ directory. The application server examines the JAR file at runtime when the server boots up to determine which EJB containers must be created and attached to the runtime.
Creating a Client Application
Now that the Cabin entity and TravelAgent EJBs have been
deployed, we want to access them from a remote client. In this
section, we create a remote client that connects to the EJB server,
locates the EJB remote interface for the TravelAgent EJB, and
interacts with the TravelAgent EJB to create and pull Cabin entities
from the database. The following code shows a Java application that
creates a new Cabin entity, sets its name,
deckLevel, shipId
, and bedCount
properties, and then locates it
again using its primary key:
package com.titan.clients; import com.titan.travelagent.TravelAgentRemote; import com.titan.domain.Cabin; import javax.naming.InitialContext; import javax.naming.Context; import javax.naming.NamingException; import java.util.Properties; import javax.rmi.PortableRemoteObject; public class Client { public static void main(String [] args) { try { Context jndiContext = getInitialContext( ); Object ref = jndiContext.lookup("TravelAgentBean/remote"); TravelAgentRemote dao = (TravelAgentRemote) PortableRemoteObject.narrow(ref,TravelAgentRemote.class); Cabin cabin_1 = new Cabin( ); cabin_1.setId(1); cabin_1.setName("Master Suite"); cabin_1.setDeckLevel(1); cabin_1.setShipId(1); cabin_1.setBedCount(3); dao.createCabin(cabin_1); Cabin cabin_2 = dao.findCabin(1); System.out.println(cabin_2.getName( )); System.out.println(cabin_2.getDeckLevel( )); System.out.println(cabin_2.getShipId( )); System.out.println(cabin_2.getBedCount( )); } catch (javax.naming.NamingException ne){ne.printStackTrace( );} } 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); } }
To access an enterprise bean, a client starts by using JNDI to
obtain a directory connection to a bean’s container. JNDI is an
implementation-independent API for directory and naming systems.
Every EJB vendor must provide a directory service that is
JNDI-compliant. This means that they must provide a JNDI service
provider, which is a piece of software analogous to a driver in
JDBC. Different service providers connect to different directory
services—not unlike JDBC, where different drivers connect to
different relational databases. The getInitialContext( )
method uses JNDI to
obtain a network connection to the EJB server.
The code used to obtain a JNDI context depends on which EJB vendor you use. Consult your vendor’s documentation to find out how to obtain a JNDI context appropriate to your product. For example, the code used to obtain a JNDI context in WebSphere might look something like the following:
public static Context getInitialContext( ) throws javax.naming.NamingException { java.util.Properties properties = new java.util.Properties( ); properties.put(javax.naming.Context.PROVIDER_URL, "iiop:///"); properties.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.ibm.ejs.ns.jndi.CNInitialContextFactory"); return new InitialContext(properties); }
The same method developed for JBoss would be different:
public static Context getInitialContext( ) throws javax.naming.NamingException { Properties p = new Properties( ); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); p.put(Context.URL_PKG_PREFIXES, " org.jboss.naming:org.jnp.interfaces"); p.put(Context.PROVIDER_URL, "jnp://localhost:1099"); return new javax.naming.InitialContext(p); }
Once a JNDI connection is established and a context is
obtained from the get-InitialContext(
)
method, the context can be used to look up the remote
interface of the TravelAgent EJB:
Object ref = jndiContext.lookup("TravelAgentBean/remote");
Throughout this book, we’ll use lookup names like "TravelAgentBean/remote"
for remote client
applications. The actual name you use to do a lookup may be
different, depending on the requirements of your vendor. You will
need to bind a lookup name to the EJB server’s naming service, and
some vendors may require a special directory path or provide a
default binding.
If you are using a standard Java EE component (Servlet, JSP,
EJB, or Java EE Application Client), you will not need to set the
properties explicitly when creating a JNDI InitialContext
, no matter which EJB vendor you are using.
That’s because the JNDI properties can be configured at deployment
time and are applied automatically. A Java EE component would obtain
its InitialContext
as
follows:
public static Context getInitialContext( ) throws javax.naming.NamingException { return new javax.naming.InitialContext( ); }
This is simpler and more portable than configuring JNDI
properties for simple Java clients. All Java EE components use the
same JNDI naming system that enterprise beans use to look up any
service. Specifically, they require that EJB references be bound to
the "java:comp/env/ejb/"
namespace. For example, for a different Java EE component like a
servlet, here’s all we need to look up the TravelAgent EJB:
Object ref = jndiContext.lookup("java:comp/env/ejb/TravelAgentRemote");
At deployment time, you would use the vendor’s deployment
tools to map that JNDI name to the TravelAgent EJB’s remote
interface. In later sections, we’ll see that special annotations
allow you to inject EJB references directly into the bean class. We
already saw an example of one of them when the EntityManager
service was injected into
the TravelAgentBean
class. In
this book, Java client applications will need to use explicit
parameters for JNDI lookups. As an alternative, you could use a
special Java EE component called a Java EE Application Client, but
this type of component is outside the scope of this book. For more
information about Java EE Application Client components, consult the
Java EE 5 specification.
The Client
application uses
the PortableRemoteObject.narrow(
)
method to narrow the Object
ref
to a TravelAgentRemote
reference:
Object ref = jndiContext.lookup("TravelAgentRemote"); CabinHomeRemote home = (TravelAgentRemote) PortableRemoteObject.narrow(ref,TravelAgentRemote.class);
The PortableRemoteObject.narrow(
)
method was first introduced in EJB 1.1 and continues to
be used on remote clients in EJB 3.0. It is needed to support the
requirements of RMI over IIOP. Because CORBA supports many different
languages, casting is not native to CORBA (some languages don’t have
casting). Therefore, to get a remote reference to TravelAgentRemote
, we must explicitly
narrow the object returned from lookup(
)
.
The name used to find the TravelAgent EJB’s remote interface is set by a vendor-specific default value, a vendor-specific annotation or deployment descriptor, or the deployer using a deployment wizard if one exists for that EJB product. The JNDI name is entirely up to the person deploying the bean; it can be the same as the bean name set in the XML deployment descriptor, or something completely different.
Creating a new Cabin entity
In this example, you finally see how Cabin entities are
created. Java’s new( )
operator
is used to allocate an instance of the Cabin
bean class. Nothing magical is
happening. The client initializes the properties locally on the
Cabin
bean instance. The
id
, name
, deckLevel, shipId
, and bedCount
of the Cabin entity are set:
Cabin cabin_1 = new Cabin( ); Cabin_1.setId(1); cabin_1.setName("Master Suite"); cabin_1.setDeckLevel(1); cabin_1.setShipId(1); cabin_1.setBedCount(3);
The Cabin entity does not get inserted into the database
when you allocate it on the client. The instance must be passed to
the TravelAgent EJB where it will be created in the database when
the EntityManager.persist( )
method is called.
Figure 4-3 shows how the relational database table we created should look after this code has been executed. It should contain one record.
The client locates Cabin entity beans by passing the primary
key to the Travel-AgentRemote
’s
findCabin( )
method. As you saw
before, this session bean interacts with the EntityManager
service to find the bean
in the database. The TravelAgent passes back an instance of a
Cabin
with that primary key.
This is possible because we defined the Cabin
bean class to implement the
java.io.Serializable
interface, which allowed the Cabin
bean instance to be marshaled
across the wire back to the client. We can now interrogate the
Cabin
bean instance locally to
get the Cabin entity’s name, deckLevel,
shipId
, and bedCount
:
Cabin cabin_2 = dao.findCabin(1); System.out.println(cabin_2.getName( )); System.out.println(cabin_2.getDeckLevel( )); System.out.println(cabin_2.getShipId( )); System.out.println(cabin_2.getBedCount( ));
We are ready to create and run the Client
application. Compile the Client
application and deploy the Cabin
entity into the container system. Then run the Client
application. The output should
look something like this:
Master Suite 1 1 3
Congratulations! You just created and used your first
stateless session bean and entity bean. Of course, the Client
application doesn’t do much, but
this is a good first step to learning how to implement EJBs and
entities.
Get Enterprise JavaBeans 3.0, 5th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.