In This Chapter
As has been covered elsewhere in this book, the typical BEA WebLogic Server configuration consists of three tiers:
Tier 1: Web Server Tier
Tier 2: Application Server Tier
Tier 3: Database Tier
The Database tier is responsible for storage and retrieval of attributes and properties of the enterprise application through persistence. The application achieves the following goals when persistence is used correctly:
Durability—. The state of the application can be restored after system shutdowns or crashes.
Reliability—. The state of the application is accurate. The data is left in a consistent and known state.
Scalability—. The application can grow to support more objects without changing or affecting the architecture.
A durable, reliable, and scalable application is exactly what is provided by entity Enterprise JavaBeans. In Chapter 6, “Transitioning from Software Design to J2EE Technology Components and Services,” the Model-View-Controller design pattern was introduced. This design pattern maps directly to an enterprise application, with JSP and servlets providing the view, session EJBs providing the control, and entity EJBs providing the model. This chapter focuses on the model and how it is implemented using entity EJBs. Specifically, it focuses on the components of an entity EJB and how persistence is implemented.
As the title of this chapter suggests, entity beans are responsible for managing persistence in an enterprise application. They are the data-centric component of the architecture. Persistence is the action of storing the state of the application, allowing it to be fully restored at a later time. The life span of persistent data extends past the system shutdown, allowing it to be restored in a subsequent execution. The state of the application is defined by the value of the objects’ attributes. Some attributes are considered transient and do not take part in persistence of the application. The non-transient attributes contain the state information that must be maintained throughout system shutdowns and crashes. For example, a business application that maintains sales orders and inventory requires that these values are stored persistently.
The Java2 Enterprise Edition addresses the need for persistence through entity EJBs. When such a bean is deployed in BEA WebLogic Server, the persistent data typically resides in a relational database. The entity bean provides the object-relational mapping with the database management system (DBMS). In the multitiered architecture of an enterprise application, the business logic, provided by the session beans, uses the entity beans for persistent storage of application data.
Entity EJBs provide the following behavior for the enterprise application in that they
Represent persistent data throughout shutdowns and crashes
Are shared by multiple clients
Synchronously maintain persistent storage
Provide the object-relational mapping between the in-memory attributes of the entity bean and the persistent storage
BEA WebLogic Server provides a subsystem known as the EJB container. All Enterprise JavaBeans exist within this container. Through the EJB container, the application server provides services to allow the EJB to perform database management, transaction management, and life-cycle management. The bean writer has the option to use the EJB container to perform Container-Managed Persistence (CMP). The alternative is Bean-Managed Persistence (BMP), which requires the bean to directly interface with the database to implement persistence. The two types of entity beans, CMP and BMP, will be covered in detail along with the additional services provided by the EJB container. The container also provides Container-Managed Transactions (CMT) and Container-Managed Relationships (CMR). The combination of CMP with CMR greatly reduces the need for the bean provider to write Bean-Managed Persistence. This option frees the entity bean from providing the code to perform these services on its own behalf.
The entity EJB type is defined by the method that is used for persistence: Bean-Managed Persistence or Container-Managed Persistence.
A BMP entity EJB contains the JDBC code in its methods to directly communicate with the database. A CMP entity EJB allows the application server to perform persistence through the EJB container. In either case, the persistent state of the entity bean is stored in a relational database by mapping the attributes of the bean to rows and columns in the tables of a relational database. Both BMP and CMP EJBs provide a deployment descriptor in the form of an XML file to describe the components of the bean and the type of persistence that will be used. In the case of Container-Managed Persistence, the fields whose persistence is managed by the container are also specified in the deployment descriptor.
Container-Managed Persistence is the preferred implementation for entity beans. The first motivation is that there is far less code for the bean provider to write. This reduces development time and simplifies software maintenance. The portability and reusability of the entity EJB are also increased because the interaction with the database is not hard-coded into the EJB. The EJB class and the deployment descriptor define the specification for the CMP entity bean. An entity EJB using CMP will provide <cmp-field>
tags in its deployment descriptor for each field being persisted by the container. The corresponding setters and getters for the container-managed fields in the EJB class are written as abstract methods. The “Container-Managed Persistence Example” section later in this chapter contains sample code using abstract setters and getters. Container-Managed Persistence is generally used in conjunction with Container-Managed Relationships. The need for CMR arises when an entity bean has another entity bean as an attribute. The container must perform the loading and persistence of the encapsulated entity beans as well. The composition relationship between entity beans is specified using CMR. CMP with CMR greatly reduces the need to implement Bean-Managed Persistence.
The primary advantage of implementing BMP is having flexibility and control over persistence. Having this control over the implementation of persistence allows the bean provider to use vendor-specific extensions or stored procedures. The debugging of problems with persistence is simpler with BMP because the interaction with the database is in the entity bean, rather than in the container. In addition, the implementation may contain particularly complex relationships between the objects being persisted. For example, if very complex many-to-many relationships exist between objects, using BMP is often necessary. Another need for BMP occurs when a proprietary scheme is being used for the stored data. This is the case if something other than a relational database supported by the WebLogic Server is being used for data persistence.
The Enterprise JavaBeans 2.0 specification introduced Container-Managed Relationships. This new capability of the container, to manage complex relationships between objects, greatly reduced the need for bean providers to implement Bean-Managed Persistence. Prior to the availability of CMR, the only way to maintain complex relationships in persistent storage was for the bean providers to do it themselves, hence BMP. CMR enhances the services provided by the container, allowing the object relationships to be specified in the ejb-jar.xml deployment descriptor with the <relationships>
tag. Now CMP working hand in hand with CMR can fully specify the object-to-relational mapping in the deployment descriptor rather than the bean provider implementing these relationships in the Java code of the entity bean.
The design model that is directly addressed by CMR is a composition or aggregation relationship between entity beans. In UML terminology, this is referred to as the “has” relationship between two or more entity beans. The situation occurs when an entity bean has another entity bean as one of its attributes.
▸ For further information on composition and aggregation class relationships, see “Association Relationships,” p. 108.
Prior to the availability of CMR, the container did not handle the persistence or loading of the contained entity beans. The bean provider had to either write additional code in the ejbCreate()
, ejbActivate()
, and ejbPassivate()
methods when using CMP, or write the entire entity bean using BMP. When the composition between entity beans forms an aggregate relationship where the life cycle of the dependent object couldn’t exist outside of the context of the independent object, the implementation of ejbStore()
, ejbLoad()
, and ejbRemove()
also requires specific code to persist and restore the dependent entity bean. The EJB 2.0 specification alleviates this situation through Container-Managed Relationships.
Refer to the section “Container-Managed Persistence Example” later in this chapter for a complete description of the EntityBean
interface for CMP.
The container-managed fields of the entity bean do not appear directly in the entity bean as attributes. Instead, abstract getters and setters are used to identify the fields that are under the control of Container-Managed Persistence. The deployment descriptor contains <ejb-relation>
tags to specify the multiplicity of the relationship. A relationship with multiplicity one
will return the local interface of the contained entity bean. When the multiplicity of the relationship is many
, the return type for the getter is specified as either java.util.Collection
or java.util.Set
.
Listing 21.1 shows a snippet from the ejb-jar.xml
deployment descriptor specifying a relationship between a shopping cart and books. This is a one-to-many relationship: one shopping cart has many books. The relationship being established is a ShoppingCart
with a collection of Books
as an attribute that is also entity beans. CMP is used for the persistence of both the ShoppingCart
and the Books
. A container-managed relationship requires the specification of two relationship roles describing the relationship from the perspective of each entity. The ShoppingCart-Has-Books role has a mulitiplicity of many. The Book-Has-ShoppingCart role has a mulitiplicity of one.
Example 21.1. The ejb-jar.xml Deployment Descriptor Specifies Container-Managed Relationships in the <relationships>
Tag
<relationships> <ejb-relation> <ejb-relation-name>ShoppingCart-Book</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name>ShoppingCart-Has-Books</ejb-relationship-role-name> <multiplicity>many</multiplicity> <relationship-role-source> <ejb-name>BookStore</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>cart</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name>Book-Has-ShoppingCart</ejb-relationship-role-name> <multiplicity>one</multiplicity> <relationship-role-source> <ejb-name>ShoppingCart</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>books</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships>
Transaction management specifies when a transaction is started, suspended, resumed, committed, and rolled back. This is referred to as the demarcation of transactions. The proper use of transactions is key to maintaining reliable persistent data that can be trusted to be accurate. To ensure that the data is accurate in the database, before a group of updates is performed on the database, a transaction is started. If all updates complete successfully, the transaction is committed; otherwise, a rollback forces all updates to be discarded as a group. This guarantees that no partial modifications are made to the data. Either all updates are performed, or none are performed. Transaction management is performed using the Java Transaction API (JTA).
▸ For further information on transactions, see Chapter 16, “Managing Java Transactions Using JTA,” p. 527.
You don’t need to compare container-managed transactions versus bean-managed transactions with respect to entity beans. The EJB 2.0 specification requires that all entity EJBs use container-managed transactions. Session beans and servlets are allowed to perform bean-managed transactions by directly starting transactions through the javax.transaction.UserTransaction
object. All entity beans, whether BMP or CMP, must use container-managed transactions. The following methods must therefore never be used in an entity bean:
commit()
, setAutoCommit()
, and rollback()
methods of java.sql.Connection
getUserTransaction()
method of javax.ejb.EJBContext
Any methods of javax.transaction.UserTransaction
The XML deployment descriptor contains the declarative rules for specifying transaction demarcation. Six keywords are used to specify the rules for transaction demarcation. The transaction is applied on a per-method basis of the entity bean. The deployment descriptor allows a wildcard specification of the method name to set the same default transaction management to all methods of the entity bean. Individual methods may still override the default behavior by setting a different transaction attribute for those methods. Table 21.1 lists the transaction attributes.
Table 21.1. Keyword Definitions for Transaction Attributes
The deployment descriptor, ejb-jar.xml
, defines the attributes for container-managed transactions. Listing 21.2 shows a snippet from a deployment descriptor that specifies the Required
transaction attribute for all methods in the ShoppingCartEJB
entity bean. The <container-transaction>
elements are children of the <assembly-descriptor>
.
Example 21.2. Sample Snippet of a Container Transaction from ejb-jar.xml
<container-transaction> <method> <ejb-name>ShoppingCart</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction>
Individual methods of the ShoppingCart
entity bean may override the default transaction attribute by adding additional <container-transaction>
specifications as children of the <assembly-descriptor>
(see Listing 21.3).
Example 21.3. Container Transaction for a Specific Method
<container-transaction> <method> <ejb-name>ShoppingCart</ejb-name> <method-name>addBook</method-name> <method-params> <method-param>com.objectmind.BookStore.BookEntityVO</method-param> </method-params> </method> <trans-attribute>Supports</trans-attribute> </container-transaction>
Container-managed transactions are demarcated on method call boundaries. The transaction attribute specifies the action performed by the EJB container when the method is called. If a new transaction is started, the transaction is committed when the method returns.
The transaction demarcation is defined by the <trans-attribute>
specification for the methods of the entity bean. Every transaction is either committed or rolled back. The EJB container will roll back container-managed transactions under two conditions. First, if a RuntimeException
is thrown after the transaction is started, the transaction is automatically rolled back. Second, invoking the setRollbackOnly()
method on the EJBContext
enables the bean provider to cause a rollback when an application-specific exception is thrown. For example, the BookStoreEJB
will roll back the transaction if the requested book is out of stock in the purchase method. Entity beans are provided with the javax.ejb.EntityContext
through their setEntityContext()
and unsetEntityContext()
methods.
For example, the purchase()
method for a BookStoreEJB
is written, as shown in Listing 21.4, to throw an application-specific exception and roll back the container-managed transaction if the book is out of stock.
Example 21.4. The setRollbackOnly()
Method of EJBContext
Is Used to Roll Back a Container-Managed Transaction
public void addBook(BookEntityVO bookData) throws NamingException, CreateException, FinderException { try { // use JNDI to lookup the Home interface for Book Context jndiContext = new InitialContext(); BookEntityHome bookHome = (BookEntityHome)jndiContext.lookup("cmp.BookEntity"); // make sure the book is in stock before adding // it to the shopping cart BookEntity book = bookHome.findBookByTitle( bookData.getTitle() ); // create a new Book entity book = bookHome.create(bookData); // add the Book reference to our book list Collection books = getBooks(); books.add(book); } catch( FinderException x ) { ctx.setRollbackOnly(); throw x; } catch( RemoteException x ) { } }
After a transaction has been set to the “rolled back” state, all persistent activities thereafter will be rolled back for this transaction. Therefore, as a best practice the bean provider might want to check the state of the transaction using the getRollbackOnly()
method on the EJBContext
before further processing.
From the perspective of the bean provider, the entity bean exists when the create()
method of the home interface is called. The create()
method of the remote home interface returns a reference to the remote interface. The create()
method of the local home interface returns a reference to the local interface.
The components of the entity bean are discussed in more detail in the next section. This early introduction to the remote and local home interfaces and the remote and local interfaces aids in the discussion of the lifecycle of an entity bean. All four of these components are interfaces, not classes. The implementation classes for the entity bean interfaces are generated by WebLogic Server when the bean is deployed. The lifecycle of the entity bean object is controlled by the WebLogic EJB container. As shown in Figure 21.1, the container maintains a pool of objects that are reused and dynamically associated with an entity bean. This allows the container to provide high-performance bean management by pre-allocating beans at system startup.
Figure 21.1. The EJB container manages the free pool of entity beans that are dynamically assigned when the client requests the creation of an entity bean instance.
Figure 21.1 shows the sequence of events that occur in the lifecycle of an entity bean object:
The container populates the free pool with a set of Entity Beans.
The client invokes the create()
method on the home object.
The home object allocates a bean from the free pool.
The home object invokes the ejbCreate()
method on the entity bean, passing the same arguments from create()
in step 2.
The entity bean inserts a row into the database table.
The bean returns the primary key of the entity bean object to the home object.
The container creates the EJB object for the entity bean. The primary key is immediately passed to the EJB object.
The home object invokes the ejbPostCreate()
method on the entity bean to allow the entity bean to finish initialization with the existence of the EJB object.
The home object returns the remote reference of the EJB object to the client.
The client uses the remote reference to invoke business logic in the bean instance.
When the container creates the entity bean instances to populate the free pool, it provides the entity bean with a reference to the EntityContext
through the setEntityContext()
method of the EntityBean
interface. The EntityContext
gives the entity bean access to the container provided at runtime. Through methods of the EntityContext
, the entity bean can invoke getEJBLocalObject()
, getEJBObject()
, and getPrimaryKey()
. The entity bean object is said to be in the pooled state because it is not yet associated with the persistence of any particular entity bean.
When the application calls the create()
method on the home interface of an entity bean, the container selects an instance of a pooled entity bean and calls the corresponding ejbCreate()
and ejbPostCreate()
methods on the entity bean. The entity bean is now in the ready state and is used to manage persistence. The entity bean can transition back to the pooled state under three conditions:
The container calls ejbPassivate()
to disassociate the entity bean from the persistent storage.
The client calls the remove()
method on the home interface which causes the container to call the ejbRemove()
method on the entity bean.
The entity bean calls ejbRemove()
as a result of rolling back a transaction.
An entity bean that is in the pooled state as a result of passivation can transition back to the ready state through activation. To activate an entity bean that is not associated with a bean instance, the container calls the ejbActivate()
method followed by the ejbLoad()
method.
The container can remove an entity bean from the bean pool by calling the unsetEntityContext()
on the bean. This only occurs on a pooled bean, not a ready bean.
Whether the entity bean type is CMP or BMP, the components are the same; only the implementation and contents of the deployment descriptor differ. The components of an entity EJB are
Remote interface
Local interface
Remote home interface
Local home interface
Entity bean implementation
Primary key
Deployment descriptors
The local interfaces for entity EJBs were introduced with EJB 2.0. Previously, only session beans provided local interfaces. The local interface provides a high-performance communication path between the client and EJB when they reside within the same Java Virtual Machine. The local interface avoids the overhead associated with Remote Method Invocation (RMI), allowing the client to directly invoke methods of the EJB. This capability is particularly valuable for an entity bean that contains other entity beans as its attributes. The entity bean will communicate through the local interface, rather than the remote interface, resulting in much higher performance. For further information on RMI, see Chapter 12, “Distributed Processing Using RMI.”
Following the RMI protocol, the components of an EJB include a remote interface, home interface, and remote object. The EJB remote interface is the proxy used by the client to communicate with the EJB remote object. The EJB home interface is the factory to create an instance of an object that implements the remote interface. The EJB class, remote interface, and home interface are the components used to establish communication between the EJB client and the entity EJB. The remaining components for an entity EJB are the primary key classes and deployment descriptors. Unlike a session EJB, a primary key class is a required component for an entity EJB.
An entity EJB establishes and maintains a mapping to persistent storage in a database. When the entity bean is created, through the create()
method of the home interface, a corresponding database insert operation is performed. When the entity bean is removed, through the remove()
method of the home interface, a database delete operation is performed. A simple entity bean will map to a single row in a database table. The attributes of the entity bean correspond to the columns in the table. More complex implementations can be mapped by the bean provider, using Bean-Managed Persistence, or by the container using Container-Managed Persistence with Container-Managed Relationships.
In order to develop the components for an EJB, the Java2 Enterprise Edition is required. Specifically, j2ee.jar
contains the classes in the javax.ejb
package. Your EJB components establish inheritance and composition relationships with the classes in the java.rmi
and javax.ejb
classes. It is valuable to gain an understanding of the J2EE interfaces that are being used by the EJB. Table 21.2 provides a brief description of the API for selected interfaces that are used by entity bean providers from the javax.ejb package.
WebLogic Server 7.0 provides the J2EE classes in eaweblogic700serverlibweblogic.jar. This jar file replaces j2ee.jar when developing on WebLogic Server.
Table 21.2. The API for Selected Interfaces from the javax.ejb
Package
Description | |
---|---|
javax.ejb.EJBObject | Public interface EJBObject |
Extends java.rmi.Remote | |
The remote interface of an EJB must extend the EJBObject interface. Refer to the “EJB Remote Interface” section later in this chapter for more information. | |
javax.ejb.EJBHome | Public interface EJBHome |
Extends java.rmi.Remote | |
The remote home interface of an EJB must extend the EJBHome interface. Refer to the “EJB Remote Home Interface” section later in this chapter for more information. | |
javax.ejb.EntityBean | Public interface EntityBean |
Extends EnterpriseBean | |
The EntityBean interface is implemented by every entity enterprise bean class. The container uses the EntityBean methods to notify the enterprise bean instances of the instance’s life-cycle events. | |
The entity EJB class must implement the EntityBean interface. Refer to the “Entity EJB Class” section later in this chapter for more information. | |
javax.ejb.EntityContext | Public interface EntityContext |
Extends EJBContext | |
The entity EJB accesses the container runtime context through the EntityContext interface. Refer the “Bean-Managed Persistence Example” and the “Container-Managed Persistence Example” sections later in this chapter for more information. |
The remote interface defines the public API for the entity bean. The bean provider writes the remote interface, and the WebLogic EJB container implements the interface when the bean is deployed. The purpose of an entity EJB is to provide persistent storage for the enterprise application. The methods in the remote interface are therefore getter and setter methods for the attributes in the data model of this entity bean. The remote interface must be public and must extend the javax.ejb.EJBObject
interface. Following the requirements of RMI, each method in the remote interface must declare that it throws RemoteException
. As with any JavaBean interface, the designer chooses which attributes are read/write and which are read-only. The read/write attributes will have get
and set
methods; the read-only attributes will have only a get
method.
The bookstore example was first presented in Chapter 13, “Accessing Data Repositories Using JDBC.” In the new example, shown in Listing 21.5, a book is created with a title, author, and price. After the book is created, the title and author cannot be changed. These will be read-only attributes. The only attribute that can be changed is the price. This API determines that title and author will have only get
methods; the price will have get
and set
methods.
Example 21.5. Remote Interface for the BookEntity
EJB
/** * Remote interface for the BookEntity EJB */ package com.objectmind.BookStore; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface BookEntity extends EJBObject { public String getTitle() throws RemoteException; public String getAuthor() throws RemoteException; public float getPrice() throws RemoteException; public void setPrice( float price ) throws RemoteException; }
There are only two differences between the remote interface and the local interface. The local interface
Extends EJBLocalObject
Does not throw RemoteException
Listing 21.6 shows the implementation of the local interface for the Book entity bean. Other than these two differences, the BookEntity and BookEntityLocal interfaces declare the same methods.
Example 21.6. Local Interface for the BookEntity
EJB
/** * Local interface for the BookEntity EJB */ package com.objectmind.BookStore; import javax.ejb.EJBLocalObject; public interface BookEntityLocal extends EJBLocalObject { public String getTitle(); public String getAuthor(); public float getPrice(); public void setPrice( float price ); }
In a multitier architecture, each invocation of an EJB method (Session or Entity bean) from the presentation tier, is a network overhead. As the number of remote method invocations increases, the probability for the application to incur a degrade in performance also increases. In such a scenario, a more performance-oriented approach to configure an entity bean is to create a value object class. The value object is written as a standard JavaBean with private attributes and public getters and setters. A common naming convention is to add the letters VO to the end of the name of the entity bean class. BookEntityVO
would therefore be the value object for the BookEntity
EJB. The attributes of the value object are the same as the attributes of the entity bean. Rather than passing individual parameters to the create()
method of the home interface, you pass the single value object, which reduces the number of remote method calls. Following this implementation, the ejbCreate()
and ejbPostCreate()
methods of the entity bean would also have the value object as their single parameter.
Rather than passing many parameters to create()
, ejbCreate()
, and ejbPostCreate()
, implement a simple JavaBean to act as a value object.
The value object for the BookEntity
EJB contains the title, author, price, and ID for the book (see Listing 21.7).
Example 21.7. Sample Value Object for the BookEntity EJB
/** * Value object for the BookEntity EJB */ package com.objectmind.BookStore; import java.io.Serializable; public class BookEntityVO implements Serializable { private String bookID; private String title; private String author; private float price; public String getBookID() { return bookID; } public void setBookID(String value) { bookID = value; } public String getTitle() { return title; } public void setTitle(String value) { title = value; } public String getAuthor() { return author; } public void setAuthor(String value) { author = value; } public float getPrice() { return price; } public void setPrice(float value) { price = value; } }
The EJB remote home interface is a factory that is responsible for providing an instance of an object that implements the remote interface. The reference is either created as a new instance using the create()
method, or an existing instance can be located using finder methods. Only remote home interfaces provide finder methods to locate and use existing beans. This allows multiple clients to share the same entity bean. As with the remote interface, the bean provider writes the home interface, and the WebLogic EJB container implements the home interface when the bean is deployed.
A common naming convention for the home interface is to simply append Home to the name of the remote interface. The home interface for the BookEntity
is therefore BookEntityHome
. The home interface must be a public interface that extends javax.ejb.EJBHome
.
The create()
method in the home interface is effectively a constructor. The parameters to create()
are application dependent and used to initialize the EJB. The return type for create()
is the remote interface, which extends EJBObject. The calling client then invokes methods on the bean via the remote interface. Refer to Figure 21.1 for a diagram showing the EJBObject and the EJB entity bean. The create()
method is declared with a throws
clause for javax.ejb.CreateException
and java.rmi.RemoteException
.
The home interface also provides finder methods to locate instances of existing entity EJBs. There must be a method named findByPrimaryKey()
whose parameter is the same type as the primary key. Additional finder methods are implemented to provide application-specific features for the API of the entity EJB. For example, BookEntityHome
provides a finder method to return a collection of all books by a given author. The return type for the finder methods is the remote interface for a single instance match, or a java.util.Collection
to return a list of matching instances.
The create()
method in the home interface may be overloaded for different parameter lists. The EJB class must have ejbCreate()
methods along with their corresponding ejbPostCreate()
methods overloaded with the same parameter lists as the create()
methods.
Listing 21.8 includes examples for both return types from finder methods. The findByPrimaryKey()
method returns the Book remote interface of the object that has the unique primary key. The findBooksByAuthor()
method returns a java.util.Collection
of remote objects matching the requested author.
For BMP, the bean provider must implement the finder methods declared in the home interface through the coding of its corresponding ejbFind
... methods. Although, in CMP, the ejbFind
... methods cannot be declared in the bean, the methods must be declared in the bean’s home interface. The container will provide the implementation.
Example 21.8. Sample Remote Home Interface for the BookEntity
EJB
/** * BookEntityHome is the home interface for the BookEntity EJBObject. */ package com.objectmind.BookStore; import java.rmi.RemoteException; import java.util.Collection; import javax.ejb.EJBHome; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface BookEntityHome extends EJBHome { /** * Create method for BookEntity * @param value value object BookEntityVO * @return BookEntity EJBObject */ public BookEntity create(BookEntityVO value) throws CreateException, RemoteException; /** * Finder method by primary key * @param key book title is the primary key * @return Book object matching primary key */ public BookEntity findByPrimaryKey( BookEntityPK key ) throws FinderException, RemoteException; /** * Finder method to create a list of books by author * @param author book author to match * @return collection of BookEntity objects in a Vector */ public Collection findBooksByAuthor( String author ) throws FinderException, RemoteException; /** * Finder method to find a book by title * @param title book title to match * @return collection of BookEntity objects in a Vector */ public BookEntity findBookByTitle( String title ) throws FinderException, RemoteException; }
There are only three differences between the remote home interface and the local home interface. The local home interface
Extends EJBLocalHome
Creates and finds the local interface
Does not throw RemoteException
Listing 21.9 shows the implementation of the local home interface for the Book entity bean. Other than these three differences, the BookEntityHome
and BookEntityLocalHome
interfaces declare the same methods.
Example 21.9. Local Home Interface for the BookEntity
EJB
/** * BookEntityLocalHome is the home interface for the BookEntity EJBObject. */ package com.objectmind.BookStore; import java.util.Collection; import javax.ejb.EJBLocalHome; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface BookEntityLocalHome extends EJBLocalHome { /** * Create method for BookEntity * @param value value object BookEntityVO * @return BookEntity EJBObject */ public BookEntityLocal create(BookEntityVO value) throws CreateException; /** * Finder method by primary key * @param key book title is the primary key * @return BookEntity object matching primary key */ public BookEntityLocal findByPrimaryKey( BookEntityPK key ) throws FinderException; /** * Finder method to create a list of books by author * @param author book author to match * @return collection of BookLocal objects */ public Collection findBooksByAuthor( String author ) throws FinderException; /** * Finder method to find a book by title * @param title book title to match * @return collection of BookEntity objects in a Vector */ public BookEntityLocal findBookByTitle( String title ) throws FinderException; }
The EJB class provides the API for mapping the attributes of the entity bean to the persistent storage. The naming convention for the EJB class used in this chapter is to append BMPBean or CMPBean to the name of the remote interface. The EJB class for Book
is therefore BookBMPBean
for the BMP example and BookCMPBean
for the CMP example. The EJB class must be a public class that implements the javax.ejb.EntityBean
interface.
The code written by the bean provider for the implementation of the entity bean is significantly different depending on whether BMP or CMP is being used to manage persistence. The first notable difference is that a CMP entity EJB is written as an abstract class, whereas a BMP entity EJB is fully implemented by the bean provider as a concrete class. This is true because the getters and setters for the container-managed fields are abstract methods when CMP is used.
Refer to the “Bean-Managed Persistence Example” for further information on implementing BMP, and the “Container-Managed Persistence Example” for further information on implementing CMP.
Entity EJBs must include a primary key that is used to uniquely identify an instance of the entity bean. The entity bean returns an instance of the primary key class from the ejbCreate()
method. The primary key class may be a standard Java class, such as java.lang.String
or java.lang.Integer
.
The bean provider may optionally provide a specific primary key class. The naming convention for the primary key class is to add the letters PK to the end of the remote interface name. The primary key for BookEntity
is therefore BookEntityPK
. The following rules must be followed when implementing a specific primary key class; it must
Implement java.io.Serializable
.
Be constructed with the public default constructor (no parameters).
Contain a public attribute that has the same name and type as the primary key attribute in the entity bean.
Override the equals()
method to compare based on the primary key attribute.
Override the hashCode()
method to generate the hashcode based on the value of the primary key.
If the EJB deployment descriptor uses <automatic-key-generation>
, the primary key type must be java.lang.Integer
. Do not write a specific primary key class for the entity bean if you intend to use automatic key generation. This option is explained in the section, “The weblogic-cmp-rdbms-jar.xml Persistence Descriptor” later in this chapter.
The primary key for BookEntity
is the bookId
field, which is a String
. The implementation for the specific primary key class for BookEntity
contains the bookId
as a public attribute and overrides the equals()
and hashCode()
methods. Listing 21.10 shows the implementation of the primary key class for the BookEntity EJB.
Example 21.10. Sample Primary Key Class for the BookEntity
EJB
/** * Primary Key for the BookEntity EJB */ package com.objectmind.BookStore; import java.io.Serializable; public class BookEntityPK implements Serializable { // primary key attribute public String bookID; /** * override equals() to compare against bookId */ public boolean equals( Object that ) { if((that == null ) || !(that instanceof BookEntityPK)) return false; return bookID.equals( ((BookEntityPK)that).bookID ); } /** * override hashCode() */ public int hashCode() { return bookID.hashCode(); } }
The deployment descriptor has the name ejb-jar.xml
. The XML tags in the deployment descriptor specify the following information for an entity EJB:
EJB name
EJB home interface
EJB remote interface
EJB class
Persistence type
CMP Fields
Primary key
Query methods
The following sections provide the definition and usage for each of these XML tags.
The EJB name is the unique name that identifies the EJB. This is the name that is used by the client to locate the entity EJB through the Java Naming and Directory Interface (JNDI).
The session bean client must look up the home interface of the entity EJB using JNDI. This name is specified in the deployment descriptor file and is bound to the corresponding home interface by WebLogic Server in the JNDI tree during deployment of the EJB.
The XML tag for the EJB name is
<ejb-name>EntityBeanEJB</ejb-name>
In addition to the EJB name, the deployment descriptor specifies the names of the home interface, remote interface, EJB class, and persistence type. In the case of a CMP entity bean, the container-managed fields are also specified. A brief description of the XML tags with a sample of the tag follows. Examples of complete deployment descriptors are provided in “Creating the BMP Style Deployment Descriptor” and “Creating the CMP Style Deployment Descriptor” later in this chapter.
The XML tag for the EJB home interface is
<home>package_name.EntityBeanHome</home>
The XML tag for the EJB remote interface is
<remote>package_name.EntityBean</remote>
The XML tag for the CMP persistence type is
<persistence-type>Container</persistence-type>
The XML tag for the BMP persistence type is
<persistence-type>Bean</persistence-type>
The XML tag for the EJB primary key field is
<primkey-field>prim_key_field_name</primkey-field>
When using CMP, the primary key must be a cmp-field
:
<cmp-field> <field-name>myPrimaryKey</field-name> </cmp-field> <primkey-field>myPrimaryKey</primkey-field>
The finder methods allow multiple clients to share an entity EJB. This is a difference between session beans and entity beans. Sessions beans cannot be shared and therefore do not have finder methods. All entity beans must contain a findByPrimaryKey()
method in their home interfaces. The findByPrimaryKey()
method in the remote home interface will return an instance of the remote interface. The findByPrimaryKey()
method in the local home interface will return an instance of the local interface. The parameter to findByPrimaryKey()
is used to match against the primary key of an existing instance of the entity bean. The deployment descriptor of the entity bean specifies the <prim-key-class>
. The parameter must therefore be of the same class type as the primary key class. In a BMP implementation, the entity bean will have a corresponding method named ejbFindByPrimaryKey()
that takes the same parameter as findByPrimaryKey()
; however, the ejbFindByPrimaryKey()
return type is the primary key where the findByPrimaryKey()
return type is the remote interface.
The bean provider can supply additional finder methods in the home interfaces, both local and remote. The return type must be the same as the return type for findByPrimaryKey()
. The parameters are application specific to provide convenient access for multiple clients to share entity beans. Each findBy
XXX
()
method in the home interface will have a corresponding ejbFindBy
XXX
()
method in the entity bean. The return value for ejbFindBy
XXX
()
is the primary key of the matching entity bean. In a BMP implementation, the ejbFindBy
XXX
()
methods will execute SQL SELECT
statements to find the matching entry in the relational database. Refer to the “Bean-Managed Persistence Example” later in this chapter for an example of the findByPrimaryKey()
method.
For Container-Managed Persistence, EJB 2.0 introduced EJB-QL, the Enterprise JavaBean Query Language. This standard query language enhances portability over proprietary query languages. The WebLogic Query Language (WLQL) provides extensions to EJB-QL; however, you cannot use WLQL with entity beans that use EJB 2.0 features or EJB 2.0 deployment descriptors. Rather than coding SELECT
statements directly in the entity bean, the deployment descriptor contains an <ejb-ql>
tag that specifies the query to be performed using EJB-QL syntax. The ShoppingCart in the “Container-Managed Relationship Example” uses EJB-QL to implement the findAll()
method that returns a java.util.Collection
of all the books in the shopping cart. The following components provide the implementation of a finder method that uses EJB-QL; it must
Provide application-specific finder methods in the home interfaces
Include the <ejb-ql>
tag following the <query-method>
in the deployment descriptor
An example of an EJB-QL query method is shown in Listing 21.11. This example was extracted as a snippet from the deployment descriptor for the book store example
.
Example 21.11. EJB-QL Allows You to Place the SQL Statement for Finder Methods in the Deployment Descriptor
<query> <query-method> <method-name>findAll</method-name> <method-params> </method-params> </query-method> <ejb-ql><![CDATA[SELECT OBJECT(s) FROM ShoppingCartSchema AS s]]></ejb-ql> </query>
The XML syntax uses the special tag <![CDATA[...]]>
. It tells the XML parser to treat the EJB-QL statement literally and to not attempt to parse it as XML. The EJB-QL statement is entered between the square brackets of the CDATA
.
This BMP example illustrates what occurs when the methods of an entity EJB are invoked. The features that are common for all BMP entity beans are that they
Implement the EntityBean
interface
Maintain an EntityContext
attribute
Retrieve the primary key from the EntityContext
Create a connection with the data source
Insert a row into the database for ejbCreate()
Refresh attributes from the database for ejbLoad()
Update a row in the database for ejbStore()
Delete a row in the database for ejbRemove()
Select rows for ejbFind
XXX
()
Initialize resources from ejbActivate()
Release resources from ejbPassivate()
Provide getters and setters for attributes
When an entity EJB is created, it establishes a connection with a data source. The connection is used to query, store, and retrieve the persistent data that is being mapped by the entity EJB. As shown in Chapter 13, “Accessing Data Repositories Using JDBC,” the connection may be established using the DriverManager
directly. Chapter 16, “Managing Java Transactions Using JTA,” introduced the DataSource
and TxDataSource
interfaces as features of the JDBC Optional Package. The DataSource
or TxDataSource
is located by performing a JNDI lookup on the configured name for the object. This isolation from the JDBC driver further increases portability and database independence. The applicaton obtains the database connection from the DataSource
instance. In addition to these two methods of establishing a connection with a data source, Chapter 13 showed the configuration for the JDBC Data Source Factory. The DataSource Factory is an instance of a data source resource bound to the JNDI tree. The entity EJB performs a lookup on the JNDI tree to gain access to this resource.
A resource manager is an object that controls access to an enterprise resource. The JDBC DataSource Factory is used to create a connection to a resource manager. The ejb-jar.xml
deployment descriptor specifies the resource reference, as shown in Listing 21.12.
Example 21.12. The ejb-jar.xml
Deployment Descriptor May Contain a Specification for a Resource Reference
<resource-ref> <resource-ref-name>jdbc/ExampleDSRef</resource-ref-name> <res-type>javax.sql.Datasource</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable<res-sharing-scope> <resource-ref>
The weblogic-ejb-jar.xml
deployment descriptor specifies the JNDI name bound to the resource connection factory. Listing 21.13 shows the reference descriptor snippet.
Example 21.13. The weblogic-ejb-jar.xml
Deployment Descriptor Specifies the JNDI Name that Is Bound to the Resource Connection Factory
<reference-descriptor> <resource-description> <res-ref-name>jdbc/ExampleDSRef</res-ref-name> <jndi-name>ExampleDS</jndi-name> <resource-description> </reference-descriptor>
With the resource reference specified in the deployment descriptors, the EJB can look up the resource connection factory using JNDI. The code snippet used by the EJB to lookup the resource connection factory to obtain a DataSource is shown in Listing 21.14.
The behavior of an entity EJB is defined by the EntityBean
interface. The implementation of the entity bean methods defines the object-relational mapping. The BookEntityBean
first shown in Chapter 13 focused on the JDBC API. In Listing 21.15, the focus is turned toward the EntityBean
interface as implemented using BMP. As mentioned in the section “Container-Managed Persistence Versus Bean-Managed Persistence,” the BMP implementation gives the bean provider full control and flexibility over the connection with the database.
Example 21.15. An Entity EJB Using BMP Takes Full Control over the Interaction with the Database
/** * BookEntityBMPBean example of a BMP Entity EJB */ package com.objectmind.BookStore; import java.util.Vector; import java.util.Collection; import javax.ejb.*; import javax.naming.InitialContext; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.DriverManager; import java.sql.Connection; import java.sql.PreparedStatement; /** * @ejbHome <{BookEntityHome}> * @ejbRemote <{BookEntity}> * @ejbPrimaryKey <{BookEntityPK}> * @ejbTransactionAttribute Required * @persistent*/ // must implement the EntityBean interface public class BookEntityBMPBean implements EntityBean { // the WebLogic Server will provide a context private EntityContext context; // object attributes public String bookID; // primary key private String title; private String author; private float price; public void setEntityContext(EntityContext context) throws javax.ejb.EJBException { // save the EntityContext this.context = context; } public void unsetEntityContext() throws javax.ejb.EJBException { // clear the EntityContext context = null; } /** ejbActivate is used to initialize any resources * required by the bean, files, sockets, etc... */ public void ejbActivate() throws javax.ejb.EJBException { } /** ejbPassivate is used to release any resources used by the bean */ public void ejbPassivate() throws javax.ejb.EJBException { } /** * ejbCreate() must be overloaded identical with the create() * methods of the home interface. The parameter list must match * the parameter list of the create() method in the home interface. */ public BookEntityPK ejbCreate(BookEntityVO value) throws javax.ejb.CreateException, javax.ejb.EJBException { bookID = value.getBookID(); title = value.getTitle(); author = value.getAuthor(); price = value.getPrice(); // insert a row into the database containing // this object's attributes try { Connection con = openConnection(); PreparedStatement ps = con.prepareStatement( "INSERT INTO books " + "( book_id, book_name, book_author, book_price ) " + "VALUES ( ?, ?, ? )" ); ps.setString( 1, bookID ); ps.setString( 2, title ); ps.setString( 3, author ); ps.setFloat ( 4, price ); if( ps.executeUpdate() != 1 ) { throw new CreateException( "Failed to create a row in the database" ); } ps.close(); // return the new primary key on success BookEntityPK pk = new BookEntityPK(); pk.bookID = bookID; return pk; } catch( Exception x ) { throw new CreateException( x.getMessage() ); } } /** * ejbPostCreate() must be overloaded identical with the * ejbCreate() methods. The ejbPostCreate() is automatically * called after ejbCreate() */ public void ejbPostCreate(BookEntityVO value) { // nothing more to do } /** * ejbRemove() deletes row with matching primary key */ public void ejbRemove() throws RemoveException { try { // always open a new connection Connection con = openConnection(); PreparedStatement ps = con.prepareStatement( "DELETE FROM books " + "WHERE book_id = ?" ); ps.setString( 1, bookID ); if( ps.executeUpdate() != 1 ) { throw new CreateException( "Failed to delete book with id " + bookID ); } ps.close(); } catch( Exception x ) { throw new RemoveException( x.getMessage() ); } } public void ejbStore() throws javax.ejb.EJBException { } public void ejbLoad() throws javax.ejb.EJBException { } // getters and setters for object attributes public String getBookID(){ return bookID; } public void setBookID(String param){ this.bookID = param; } public BookEntityPK ejbFindByPrimaryKey(BookEntityPK pk) throws javax.ejb.FinderException, javax.ejb.EJBException { // Write your code here // refresh this object's attributes by searching the database // for the primary key if( pk == null ) { throw new FinderException( "primary key cannot be null" ); } try { Connection con = openConnection(); PreparedStatement ps = con.prepareStatement( "SELECT author_id, book_name FROM books " + "WHERE book_id = ?" ); ps.setString( 1, pk.bookID ); ps.executeQuery(); ResultSet rs = ps.getResultSet(); if( rs.next() ) { bookID = pk.bookID; title = rs.getString( 2 ); } else { throw new FinderException( "Could not find " + pk.bookID ); } ps.close(); } catch( Exception x ) { throw new FinderException( x.getMessage() ); } return pk; } public Collection ejbFindBooksByAuthor(String author) throws javax.ejb.FinderException, javax.ejb.EJBException { PreparedStatement ps = null; try { Connection con = openConnection(); ps = con.prepareStatement( "SELECT book_id FROM books " + "WHERE author = ?" ); ps.setString( 1, author ); ResultSet rs = ps.executeQuery(); Vector books = new Vector(); while( rs.next() ) { BookEntityPK key = new BookEntityPK(); key.bookID = rs.getString( 1 ); } return books; } catch( Exception x ) { throw new FinderException( x.getMessage() ); } finally { closePS( ps ); } } public BookEntityVO getBookData(){ BookEntityVO value = new BookEntityVO(); value.setBookID( bookID ); value.setAuthor( author ); value.setTitle( title ); value.setPrice( price ); return value; } public void setBookData(BookEntityVO value) { bookID = value.getBookID(); author = value.getAuthor(); title = value.getTitle(); price = value.getPrice(); } public String getTitle(){ return title; } public String getAuthor(){ return author; } public double getPrice(){ return price; } public void setPrice( float value ) { price = value; //update database record PreparedStatement ps = null; try { Connection con = openConnection(); BookEntityPK key = (BookEntityPK)context.getPrimaryKey(); ps = con.prepareStatement( "UPDATE books " + "set book_price = ? " + "WHERE book_id = ?" ); ps.setFloat( 1, value ); ps.setString( 2, key.bookID ); int rows = ps.executeUpdate(); if( rows != 1 ) { throw new EJBException( "setPrice failed to update database" ); } } catch( Exception x ) { throw new EJBException( x.getMessage() ); } finally { closePS( ps ); } } // get Connection from DataSource public Connection openConnection() throws EJBException { try { InitialContext ic = new InitialContext(); DataSource ds = (DataSource)ic.lookup("MyDataSource"); return ds.getConnection(); } catch( Exception x ) { throw new EJBException( x.getMessage() ); } } private void closePS( PreparedStatement ps ) { try { if( ps != null ) ps.close(); } catch( SQLException x ) { } } }
The ejb-jar.xml
deployment descriptor for the BookEntityBMPBean
is shown in Listing 21.16. The JAR file contains a single deployment descriptor in the META-INF directory that defines the deployment characteristics for all the EJBS in the JAR.
Example 21.16. The Deployment Descriptor for a BMP Entity Bean Specifies the Names of the Interfaces for the EJB Class and Defines the Transaction Attributes
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <!-- Generated XML! --><ejb-jar> <ejb-jar> <enterprise-beans> <entity> <ejb-name>BookEntityBMPBean</ejb-name> <home>BookEntityHome</home> <remote>BookEntity</remote> <local-home>BookEntityLocalHome</local-home> <local>BookEntityLocal</local> <ejb-class>BookEntityBMPBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>BookEntityPK</prim-key-class> <reentrant>False</reentrant> </entity> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>BookEntityBMPBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
This CMP example illustrates how much work the EJB container does for the entity bean. A CMP implementation has far less code to be implemented by the bean provider. The features that are common for all CMP entity beans are that they
Are implemented as an abstract class
Implement the EntityBean
interface
Maintain an EntityContext
attribute
Provide abstract getters and setters for attributes
Initialize the attributes by calling each of the setters from ejbCreate()
Implement stubs for ejbPostCreate()
, ejbActivate()
, ejbPassivate()
, ejbLoad()
, ejbStore()
, and ejbRemove()
A CMP entity bean provides the abstract setters and getters for the container-managed fields of the entity bean. The EJB container performs the persistence of the fields identified in the deployment descriptor as a <cmp-field>
. The container derives the type of the field from the type used with the getter and setter. For example, a name field of type String
has the <cmp-field>
in the deployment descriptor:
<cmp-field> <field-name>name</field-name> <cmp-field>
This corresponds to the abstract methods:
abstract public String getName(); abstract public void setName( String name ) ;
Listing 21.17 shows the BookEntityCMPBean
, which is an example of a CMP implementation of the BookEntity
EJB. It is an abstract class with abstract setters and getters for the attributes of the entity EJB. The CMP fields for this entity bean are specified in the ejb-jar.xml
deployment descriptor. The connection with the data source and the persistence of the container-managed fields are specified in the weblogic-ejb-jar.xml
and weblogic-cmp-rdbms-jar.xml
deployment descriptors. The deployment descriptors are described in the section, “Deployment on WebLogic Server,” later in this chapter.
Example 21.17. A CMP Entity Bean Is Implemented As an Abstract Class with Abstract Setters and Getters and Stubs for EJB Methods Provided by the Container
/** * BookEntityCMPBean example of a CMP Entity EJB */ package com.objectmind.BookStore; import com.objectmind.ShoppingCart.*; import javax.ejb.*; import java.sql.SQLException; import java.sql.DriverManager; import java.sql.Connection; import java.sql.PreparedStatement; /** * @ejbHome <{BookEntityHome}> * @ejbRemote <{BookEntity}> * @ejbPrimaryKey <{BookEntityPK}> * @ejbTransactionAttribute Required * @persistent*/ // must implement the EntityBean interface public abstract class BookEntityCMPBean implements EntityBean { // the WebLogic Server will provide a context private EntityContext ctx; public void setEntityContext(EntityContext context) throws EJBException { // save the EntityContext ctx = context; } public void unsetEntityContext() throws EJBException { // clear the EntityContext ctx = null; } /** * abstract setters and getters for container-managed fields */ abstract public BookEntityPK getBookID(); abstract public void setBookID(BookEntityPK key); abstract public String getTitle(); abstract public void setTitle(String title); abstract public String getAuthor(); abstract public void setAuthor(String author); abstract public float getPrice(); abstract public void setPrice(float title); abstract public ShoppingCart getCart(); abstract public void setCart( ShoppingCart cart ) ; /** * stubs for container-managed behavior */ public void ejbPostCreate( BookEntityVO book ) { } public void ejbActivate() throws javax.ejb.EJBException { } public void ejbPassivate() throws javax.ejb.EJBException { } public void ejbRemove() throws RemoveException { } public void ejbStore() throws javax.ejb.EJBException { } public void ejbLoad() throws javax.ejb.EJBException { } /** * initialize the bean from the value object */ public BookEntityPK ejbCreate(BookEntityVO book) { setBookData( book ); return null; } /** * get the attributes of this entity bean as a value object */ public BookEntityVO getBookData(){ BookEntityVO value = new BookEntityVO(); BookEntityPK key = getBookID(); value.setBookID( key.bookID ); value.setAuthor( getAuthor() ); value.setTitle( getTitle() ); value.setPrice( getPrice() ); return value; } /* * set the attributes of this entity bean using the value object */ public void setBookData(BookEntityVO value) { BookEntityPK key = new BookEntityPK(); key.bookID = value.getBookID(); setBookID( key ); setAuthor( value.getAuthor() ); setTitle( value.getTitle() ); setPrice( value.getPrice() ); } }
The Container-Managed Relationship is used when a composition relationship exists between CMP entity beans. This example is a shopping cart that contains the books that have been selected for purchase. The ShoppingCart
requires persistent storage to allow its life cycle to span system shutdowns and crashes. The ShoppingCart
will be implemented as a CMP entity EJB. The books in the shopping cart establish a one-to-many relationship between the ShoppingCart
and the BookEntityCMPBean
. The local interfaces of the BookEntity
will be used by the ShoppingCart
for improved efficiency and higher performance.
The remote interface shown in Listing 21.18 allows books to be added to the cart and all books to be retrieved from the cart as an array. This example uses the BookEntityVO
as a convenient way to pass all attributes in a single value object parameter. The addBook()
method is overloaded to pass an instance of a BookEntity
as the parameter.
Example 21.18. Remote Interface for the ShoppingCart
/** * Remote interface for the ShoppingCart */ package com.objectmind.ShoppingCart; import com.objectmind.BookStore.*; import java.rmi.RemoteException; import javax.ejb.EJBObject; import javax.ejb.CreateException; import javax.ejb.FinderException; import javax.naming.NamingException; public interface ShoppingCart extends EJBObject { public void addBook(BookEntityVO book) throws NamingException, CreateException, RemoteException, FinderException; public void addBook(BookEntity book) throws NamingException, CreateException, RemoteException; public BookEntityVO[] getAllBooks() throws RemoteException; }
The ShoppingCartHome
, shown in Listing 21.19, creates a reference to a ShoppingCart
for the given primary key. The finder methods find a ShoppingCart
by matching a primary key and also find all instances of ShoppingCart
.
Example 21.19. Sample Remote Home Interface for ShoppingCart
/** * ShoppingCartHome is the home interface for the ShoppingCart. */ package com.objectmind.ShoppingCart; import java.rmi.RemoteException; import java.util.Collection; import javax.ejb.EJBHome; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface ShoppingCartHome extends EJBHome { /** * Create method for ShoppingCart * @return BookEntity EJBObject */ public ShoppingCart create() throws CreateException, RemoteException; /** * findByPrimaryKey is required for all entity beans * @param primaryKey Integer * @return ShoppingCart object matching primary key */ public ShoppingCart findByPrimaryKey( Integer primaryKey ) throws FinderException, RemoteException; /** * Finder method to create a list of all shopping carts * @return collection of BookEntity objects in a Vector */ public Collection findAll() throws FinderException, RemoteException; }
The attributes of the ShoppingCart
are its primary key and a collection of books. The primary key is the cartId
of type java.lang.String
while books are represented through a java.util.Collection
. Listing 21.20 shows the CMP entity bean implementation of the shopping cart.
Example 21.20. The ShoppingCartCMPBean
Is Implemented As an Abstract Class That Uses Container-Managed Persistence
/** * ShoppingCartCMPBean example of a CMP Entity EJB */ package com.objectmind.ShoppingCart; import com.objectmind.BookStore.*; import java.util.Collection; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.EJBException; import javax.ejb.CreateException; import javax.ejb.FinderException; import javax.ejb.RemoveException; import java.rmi.RemoteException; import javax.naming.NamingException; import javax.naming.Context; import javax.naming.InitialContext; import javax.sql.DataSource; import java.sql.SQLException; import java.sql.DriverManager; import java.sql.Connection; import java.sql.PreparedStatement; /** * @ejbHome <{ShoppingCartHome}> * @ejbRemote <{ShoppingCart}> * @ejbPrimaryKey <{ShoppingCartPK}> * @ejbTransactionAttribute Required * @persistent*/ // must implement the EntityBean interface public abstract class ShoppingCartCMPBean implements EntityBean { // the WebLogic Server will provide a context private EntityContext ctx; public void setEntityContext(EntityContext context) throws EJBException { // save the EntityContext ctx = context; } public void unsetEntityContext() throws EJBException { // clear the EntityContext ctx = null; } /** * abstract setters and getters for container-managed fields */ abstract public Integer getCartID(); abstract public void setCartID(Integer id); abstract public Integer getCustomerID(); abstract public void setCustomerID( Integer id ); abstract public Collection getBooks(); abstract public void setBooks(Collection books); /** * stubs for container-managed behavior */ public void ejbActivate() throws javax.ejb.EJBException{ } public void ejbPassivate() throws javax.ejb.EJBException { } public void ejbRemove() throws RemoveException { } public void ejbStore() throws javax.ejb.EJBException { } public void ejbLoad() throws javax.ejb.EJBException { } /** * initialize the bean from the value object */ public Integer ejbCreate() { return null; } public void ejbPostCreate() { } public void addBook(BookEntityVO bookData) throws NamingException, CreateException, FinderException { try { // use JNDI to lookup the Home interface for Book Context jndiContext = new InitialContext(); BookEntityHome bookHome = (BookEntityHome)jndiContext.lookup("cmp.BookEntity"); // make sure the book is in stock before adding // it to the shopping cart BookEntity book = bookHome.findBookByTitle( bookData.getTitle() ); // create a new Book entity book = bookHome.create(bookData); // add the Book reference to our book list Collection books = getBooks(); books.add(book); } catch( FinderException x ) { ctx.setRollbackOnly(); throw x; } catch( RemoteException x ) { } } public void addBook(BookEntity book) throws NamingException, CreateException { // add the Book reference to our book list Collection books = getBooks(); books.add(book); } public BookEntityVO[] getAllBooks() { Collection books = getBooks(); return (BookEntityVO[])books.toArray(); } }
The weblogic-cmp-rdbms-jar.xml
deployment descriptor for the ShoppingCart
specifies automatic key generation for the primary key. The primary key class for the ShoppingCart
is therefore, java.lang.Integer
. Refer to the ejb-jar.xml
shown in Listing 21.22 to see the specification for the <prim-key-class>
for the ShoppingCart
. Also, refer to Listing 21.24 to see the specification for <automatic-key-generation>
in the weblogic-cmp-rdbms-jar.xml
descriptor. These topics are explained in the “Deployment on WebLogic Server” section later in this chapter.
The client of an entity EJB is typically either a session EJB, a JSP or a servlet. The Customer
class, shown in Listing 21.21, is provided here as an example to test the API for the BookEntity
and ShoppingCart
entity EJBs. The customer creates an instance of the home interface for the BookEntity
. This BookEntityHome
object is used to invoke the finder methods for books. A reference to a ShoppingCart
is obtained by looking up the ShoppingCartHome
and using it to create a ShoppingCart
. The addBook()
method of ShoppingCart
is invoked when a book is purchased.
Example 21.21. The Customer Class Tests the API for the BookEntity
and ShoppingCart
Entity EJBs
/** * BookStore customer will purchase books by * adding them to a shopping cart. */ package com.objectmind.BookStore; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.ejb.CreateException; import javax.ejb.DuplicateKeyException; import java.rmi.RemoteException; import com.objectmind.ShoppingCart.*; public class Customer { // object attributes private Context context; private ShoppingCart cart; private BookEntityHome bookFinder; private BookEntityVO bookData; public Customer( String title, String author, float price, String id ) throws NamingException, CreateException, RemoteException { // create the JNDI context context = createJNDIContext(); // create the book entity home to // use its finder methods for books bookFinder = createBookFinder(); bookData = new BookEntityVO(); bookData.setTitle( title ); bookData.setAuthor( author ); bookData.setBookID( id ); bookData.setPrice( price ); // create a sample book try { bookFinder.create( bookData ); } catch( RemoteException x ) { // the DuplicateKeyException is nested as an RemoteException System.err.println( "Book already exists" ); } catch( Exception x ) { System.err.println( x.getMessage() ); } // create a shopping cart cart = createShoppingCart(); } private Context createJNDIContext() throws NamingException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory"); env.put(Context.PROVIDER_URL,"t3://localhost:7001"); Context context = new InitialContext( env ); return context; } public BookEntityHome createBookFinder() throws NamingException, CreateException { // use JNDI to lookup the Home interface for Book BookEntityHome bookHome = (BookEntityHome)context.lookup("cmp.BookEntity"); return bookHome; } public ShoppingCart createShoppingCart() throws NamingException, CreateException, RemoteException { // use JNDI to lookup the Home interface for Book ShoppingCartHome cartHome = (ShoppingCartHome)context.lookup("cmp.ShoppingCart"); ShoppingCart cart = cartHome.create(); return cart; } public void purchase() throws Exception { cart.addBook( bookData ); // if no exceptions were thrown... System.out.println( "Thank you for purchasing " + bookData.getTitle() ); } /** * Main method to unit test the customer API */ public static void main(String[] args) { // check command line args if( args.length != 4 ) { System.err.println( "usage: Customer <title> <author> <price> <bookID>"); System.exit( -1 ); } try { String title = args[0]; String author = args[1]; float price = Float.valueOf( args[2] ).floatValue(); String bookID = args[3]; Customer customer = new Customer( title, author, price, bookID ); customer.purchase(); } catch( Exception x ) { System.err.println( "Failed to purchase "" + args[0] + """ ); } } }
The ShoppingCart
uses CMP for the cartID
field and CMR for the one-to-many relationship with books. All methods of ShoppingCart
are specified to use the Required
transaction type for CMT. These specifications are defined in the ejb-jar.xml
deployment descriptor shown in Listing 21.22.
Example 21.22. The Deployment Descriptor Provides the Specification for the Persistence and Transactions Provided by the Container
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <!-- Generated XML! --> <ejb-jar> <enterprise-beans> <entity> <ejb-name>ShoppingCart</ejb-name> <home>com.objectmind.ShoppingCart.ShoppingCartHome</home> <remote>com.objectmind.ShoppingCart.ShoppingCart</remote> <ejb-class>com.objectmind.ShoppingCart.ShoppingCartCMPBean</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>ShoppingCartSchema</abstract-schema-name> <cmp-field> <field-name>cartID</field-name> </cmp-field> <cmp-field> <field-name>customerID</field-name> </cmp-field> <primkey-field>cartID</primkey-field> <query> <query-method> <method-name>findAll</method-name> <method-params> </method-params> </query-method> <ejb-ql><![CDATA[SELECT OBJECT(s) FROM ShoppingCartSchema AS s]]></ejb-ql> </query> </entity> <entity> <ejb-name>BookStore</ejb-name> <home>com.objectmind.BookStore.BookEntityHome</home> <remote>com.objectmind.BookStore.BookEntity</remote> <local-home>com.objectmind.BookStore.BookEntityLocalHome</local-home> <local>com.objectmind.BookStore.BookEntityLocal</local> <ejb-class>com.objectmind.BookStore.BookEntityCMPBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>com.objectmind.BookStore.BookEntityPK</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>BookstoreSchema</abstract-schema-name> <cmp-field> <field-name>bookID</field-name> </cmp-field> <cmp-field> <field-name>title</field-name> </cmp-field> <cmp-field> <field-name>author</field-name> </cmp-field> <cmp-field> <field-name>price</field-name> </cmp-field> <primkey-field>bookID</primkey-field> <query> <query-method> <method-name>findBookByTitle</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </query-method> <ejb-ql> <![CDATA[SELECT OBJECT(a) FROM BookstoreSchema AS a WHERE a.title = ?1]]> </ejb-ql> </query> <query> <query-method> <method-name>findBooksByAuthor</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </query-method> <ejb-ql><![CDATA[SELECT OBJECT(a) FROM BookstoreSchema AS a WHERE a.author = ?1]]></ejb-ql> </query> </entity> </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>ShoppingCart-Book</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name>ShoppingCart-Has-Books</ejb-relationship-role-name> <multiplicity>many</multiplicity> <relationship-role-source> <ejb-name>BookStore</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>cart</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name>Book-Has-ShoppingCart</ejb-relationship-role-name> <multiplicity>one</multiplicity> <relationship-role-source> <ejb-name>ShoppingCart</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>books</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships> <assembly-descriptor> <container-transaction> <method> <ejb-name>ShoppingCart</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>BookStore</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor>
The entity bean must run within a bean container on an application server. This stage of software development, to load the software on the target environment, is referred to as deployment. At this point, the bean provider has written the following interfaces and classes:
Remote interface
Remote home interface
Local interface
Local home interface
Value object class
Entity bean class
In addition to the classes and interfaces for the entity EJB, the bean provider must also write deployment descriptors to define the deployment specifications for the bean. The deployment of an entity EJB has the following steps performed by the application server:
Generate all of the implementation classes
Set up the JNDI tree for JNDI lookups
Set up the connection pool
Set up tables in the database that match the schema
All four of these steps are performed when an entity bean is deployed. The deployment of the bean simply requires the jar file containing the implementation and descriptors to be placed in the application directory on WebLogic Server. The implementation for the interfaces and abstract classes are automatically generated by WebLogic Server when the entity bean is deployed. Optionally, the tables in the database can be created automatically through a specification in the WebLogic deployment descriptor. The EJB deployment descriptor, ejb-jar.xml
, was covered previously in this chapter. WebLogic Server supports additional deployment descriptors:
weblogic-ejb-jar.xml
weblogic-cmp-rdbms-jar.xml
Through the specification provided by the WebLogic specific deployment descriptors, the JNDI tree and relational database are configured on behalf of the deployed entity EJB.
The primary purpose of the weblogic-ejb-jar.xml
file is to specify JNDI names for the deployment of the entity bean. The JNDI names are used to set up the JNDI tree database to allow the home interface of the entity bean to be retrieved using a JNDI lookup. Session beans look up the home interface of the entity beans, and entity beans with container-managed relationships look up the home interface of the other entity beans that they contain. The major XML tags that are specified in the weblogic-ejb-jar.xml
deployment descriptor are defined in Table 21.3.
Table 21.3. The Major XML Tags for the weblogic-ejb-jar.xml
Deployment Descriptor
The weblogic-ejb-jar.xml
deployment descriptor for the ShoppingCart is provided as an example in Listing 21.23.
Example 21.23. The WebLogic Deployment Descriptor Provides the Specification for Deploying the Entity EJB on WebLogic Server
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN' 'http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd'> <!-- Generated XML! --> <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>ShoppingCart</ejb-name> <entity-descriptor> <pool> </pool> <entity-cache> <cache-between-transactions>False</cache-between-transactions> </entity-cache> <persistence> <persistence-use> <type-identifier>WebLogic_CMP_RDBMS</type-identifier> <type-version>7.0</type-version> <type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-storage> </persistence-use> </persistence> <entity-clustering> </entity-clustering> </entity-descriptor> <transaction-descriptor> </transaction-descriptor> <jndi-name>cmp.ShoppingCart</jndi-name> <local-jndi-name>cmp.ShoppingCart-Local</local-jndi-name> </weblogic-enterprise-bean> <weblogic-enterprise-bean> <ejb-name>BookStore</ejb-name> <entity-descriptor> <pool> </pool> <entity-cache> <cache-between-transactions>False</cache-between-transactions> </entity-cache> <persistence> <persistence-use> <type-identifier>WebLogic_CMP_RDBMS</type-identifier> <type-version>7.0</type-version> <type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-storage> </persistence-use> </persistence> <entity-clustering> </entity-clustering> </entity-descriptor> <transaction-descriptor> </transaction-descriptor> <jndi-name>cmp.BookEntity</jndi-name> <local-jndi-name>cmp.BookEntity-Local</local-jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
The weblogic-cmp-rdbms-jar.xml
persistence descriptor is referenced by the weblogic-ebj-jar.xml
deployment descriptor as the <type-storage>
for persistence. The major XML tags that are specified in the weblogic-cmp-rdbms-jar.xml
persistence descriptor are shown in Table 21.4.
Table 21.4. The Major Tags in the weblogic-cmp-rdbmc-jar.xml
Persistence Descriptor
The <create-default-dbms-table>
is not recommended for production-quality software. The database schema should be very precise and not automatically generated by WebLogic Server.
The weblogic-cmp-rdbms-jar
persistence descriptor for the ShoppingCart is shown in Listing 21.24.
Example 21.24. The WebLogic Persistence Descriptor Provides the Specification for Mapping the Entity EJB to the Relational Database Being Used by WebLogic Server
<!DOCTYPE weblogic-rdbms-jar PUBLIC '-//BEA Systems, Inc.// DTD WebLogic 7.0.0 EJB RDBMS Persistence//EN' 'http://www.bea.com/servers/wls700/dtd/weblogic-rdbms20-persistence-700.dtd'> <!-- Generated XML! --> <weblogic-rdbms-jar> <weblogic-rdbms-bean> <ejb-name>ShoppingCart</ejb-name> <data-source-name>TxDSBookStore</data-source-name> <table-map> <table-name>ShoppingCart</table-name> <field-map> <cmp-field>cartID</cmp-field> <dbms-column>cartID</dbms-column> </field-map> <field-map> <cmp-field>customerID</cmp-field> <dbms-column>customerID</dbms-column> </field-map> </table-map> <automatic-key-generation> <generator-type>NAMED_SEQUENCE_TABLE</generator-type> <generator-name>SHOPPING_SEQ</generator-name> <key-cache-size>10</key-cache-size> </automatic-key-generation> </weblogic-rdbms-bean> <weblogic-rdbms-bean> <ejb-name>BookStore</ejb-name> <data-source-name>TxDSBookStore</data-source-name> <table-map> <table-name>bookstore</table-name> <field-map> <cmp-field>author</cmp-field> <dbms-column>bookAuthor</dbms-column> </field-map> <field-map> <cmp-field>price</cmp-field> <dbms-column>bookPrice</dbms-column> </field-map> <field-map> <cmp-field>bookID</cmp-field> <dbms-column>bookID</dbms-column> </field-map> <field-map> <cmp-field>title</cmp-field> <dbms-column>bookTitle</dbms-column> </field-map> </table-map> <weblogic-query> <query-method> <method-name>findBooksByAuthor</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </query-method> <max-elements>0</max-elements> <include-updates>False</include-updates> </weblogic-query> </weblogic-rdbms-bean> <weblogic-rdbms-relation> <relation-name>ShoppingCart-Book</relation-name> <weblogic-relationship-role> <relationship-role-name>ShoppingCart-Has-Books</relationship-role-name> <relationship-role-map> <column-map> <foreign-key-column>cart</foreign-key-column> <key-column>cartID</key-column> </column-map> </relationship-role-map> </weblogic-relationship-role> </weblogic-rdbms-relation> <create-default-dbms-tables>True</create-default-dbms-tables> </weblogic-rdbms-jar>
The implementation of an entity EJB is released as a JAR file, which contains the compiled class files for the interfaces and classes that were created to implement the entity EJB. In addition to the compiled class files, the JAR contains the ejb-jar.xml
and weblogic-ejb-jar.xml
deployment descriptors. If the bean is implemented using CMP, the weblogic-cmp-rdbms-jar.xml
persistence descriptor must also be added to the jar file for the entity bean. The entity bean JAR can be added to an enterprise application archive (EAR) file, but this is not required to deploy the bean on WebLogic Server.
Before the entity bean can be deployed to WebLogic Server, the JDBC DataSources or Tx DataSources and associated connection pools that the entity bean has been configured to use must exist and be operational. This is especially important if you have enabled the <create-default-dbms-tables>
tag in the weblogic-cmp-rdbms-jar.xml
persistence deployment descriptor to create the database tables the CMP entity bean will use. Also, if you are deploying a CMP entity bean, the sequence table the EJB container will use to auto-generate keys will also need to exist in the target database. Only once WebLogic Server and target database have been configured correctly, the JAR or EAR file can be deployed to WebLogic Server using the weblogic.Deployer tool, WebLogic Builder or the Administration Console.
To showcase the deployment of CMP entity beans, the following steps will explain how to successfully deploy the ShoppingCart EJB (BookEntity
and ShoppingCart
CMP entity beans) developed in this chapter to WebLogic Server.
To enable you to quickly and easily deploy the ShoppingCart EJB example, the Pointbase database that comes bundled with WebLogic Server will be used as the target database. The Pointbase database uses the concept of schemas to allow you to create unique database environments, while running under one database server instance. Schemas are created for each unique database user. Hence each database user can create and manipulate database tables in their own schema. The database user and schema that will be used in the Pointbase database for the ShoppingCart EJB example will be “PBPUBLIC.”
The approach taken in this example is to start the Pointbase database with the WebLogic Server instance you are using. Using this approach you can leverage the startup parameters for the Pointbase database already provided in your StartExampleServer script file located in the %BEA_HOME%weblogic700samplesserverconfigexamples directory.
The %BEA_HOME%
directory is the root directory of the BEA software installation, for example c:ea.
An example of these startup parameters is provided in Listing 21.25.
Example 21.25. The Startup Parameters for the Pointbase Database to be Copied into Your WebLogic Server Startup Script
@rem ***************************************************************** @rem PointBase and examples domain specific configuration set SAMPLES_HOME=C:eaweblogic700samples set POINTBASE_HOME=%SAMPLES_HOME%serverevalpointbase set COMMON_CLASSES=%SAMPLES_HOME%serverstagecommon @rem Add PointBase classes to the classpath, so we can start the @rem database. set CLASSPATH="C:eaweblogic700serverlibwebservices.jar; C:eajrockit70sp2_131lib ools.jar;%POINTBASE_HOME%libpbserver42ECF183.jar; %POINTBASE_HOME%libpbclient42ECF183.jar;%CLIENT_CLASSES%;%SERVER_CLASSES% ;%COMMON_CLASSES%;%CLIENT_CLASSES%utils_common.jar" @rem Start PointBase 4.2. start "PointBase" cmd /c ""C:eajrockit70sp2_131injava" -classpath %CLASSPATH% com.pointbase.net.netServer /port:9092 /d:3 /pointbase.ini="%SAMPLES_HOME%serverconfigexamplespointbase.ini"" > "%SAMPLES_HOME%serverconfigexamplespointbase.log" 2>&1 @rem ************************************************************************
Listing 21.25, as it pertains to your WebLogic Server environment, should be copied into your WebLogic Server startup script to ensure the Pointbase database starts with your WebLogic Server instance. Once you restart your WebLogic Server, you will see a command shell appear, as illustrated in Figure 21.2, indicating the Pointbase database server has started.
The final aspect of configuring your Pointbase database for the ShoppingCart EJB example is creating the sequence table that will be used to store key values for the auto-generation of primary keys. This sequence table must contain a single row with a single column named “SEQUENCE” of datatype INTEGER (SEQUENCE INT). The SEQUENCE column holds the current sequence value.
The name of the sequence table to create for the ShoppingCart EJB example is specified by the <automatic-key-generation>
tag in the weblogic-cmp-rdbms-jar
persistence descriptor, as shown in Listing 21.26.
Example 21.26. Specifying Automatic Key Generation for a Named Sequence Table
<automatic-key-generation> <generator-type>NAMED_SEQUENCE_TABLE</generator-type> <generator_name>SHOPPING_SEQ</generator-name> <key-cache-size>100</key-cache-size> </automatic-key-generation>
In Listing 21.26,
For improved performance, BEA recommends that you set this value to a number greater than one. This setting reduces the number of calls to the database to fetch the next key value.
To easily create a table named “SHOPPING_SEQ” in your Pointbase database, follow these steps:
From a command prompt, set your Java environment, for example by executing the setEnv script provided in your WebLogic domain folder.
Change the directory to the following:
%BEA_HOME%weblogic700samplesserverevalpointbase ools
Execute the following command to launch the Pointbase Console:
StartPointBaseConsole
In the displayed login screen, as shown in Figure 21.3, enter “PBPUBLIC” for the User and Password entry field. Click OK.
Execute the following SQL statement to create the SHOPPING_SEQ sequence table:
CREATE SHOPPING_SEQ (SEQUENCE int);
Enter a row into the newly created SHOPPING_SEQ table using the following SQL statement:
INSERT INTO SHOPPING_SEQ VALUES (0);
Your Pointbase database is now configured to be used with the ShoppingCart EJB example.
The ShoppingCart EJB example will use a JDBC connection pool named CMPBeanPool
to serve as a pool of database connections to your target Pointbase database using a Tx DataSource named TxDSBookStore
.
To configure your CMPBeanPool
connection pool in your WebLogic Server:
Start the Administration Console.
From the Administration Console, expand the JDBC node in the left pane.
Click the Connection Pools node, which will display all the connection pools defined in your domain in the right pane.
Click the Configure a New JDBC Connection Pool text link. A new page will display in the right pane (Configuration, General tab) allowing you to configure a new connection pool.
Enter the following values to configure your connection pool
Name = CMPBeanPool
URL = jdbc:pointbase:server://localhost/demo
Driver Classname = com.pointbase.jdbc.jdbcUniversalDriver
Properties = user=PBPUBLIC, password=PBPUBLIC
Click Create to create the CMPBean connection pool. This new instance is added under the Connection Pools node in the left pane.
Click the Targets tab and assign the CMPBean connection pool to your WebLogic Servers or clusters.
Click Apply.
▸ For an illustrated and detailed guide to creating a JDBC connection pool, see “Configuring and Using Connection Pools,” p. 427.
To configure your “TxDSBookStore” Tx DataSource in your WebLogic Server:
Start the Administration Console
In the left pane of the Administration Console, click to expand the JDBC node.
Click the Tx DataSources node. The Tx DataSources table displays in the right pane showing all the data sources defined in your domain.
Click the Configure a New JDBC Tx DataSource text link. A new page displays in the right pane showing the Configuration tab for creating a new Tx DataSource.
Enter the following values in the Configuration tab:
Name = TxDSBookStore
JNDI Name = TxDSBookStore
Pool Name = CMPBeanPool
Click Create to create the TxDSBookStore Tx DataSource.
Click the Targets tab and assign the TxDSBookStore Tx DataSource to your WebLogic Servers or clusters.
Click Apply.
Your CMPBean connection pool and TxDSBookStore Tx DataSource is now configured for the ShoppingCart EJB example against the Pointbase database.
To deploy the ShoppingCart EJB example to your WebLogic Server, the ShoppingCart
and BookEntity
CMP entity beans, the Customer
class, and the associated deployment descriptors must be compiled and packaged into a JAR file named ShoppingCart.jar
using the following steps:
The Customer
class is used to unit test the API for the ShoppingCart
and BookEntity
entity beans later in this chapter.
Set your Java environment by executing a set environment script, for example the setEnv WebLogic Server script.
Execute the following command from your working directory to compile your Java source code:
javac -d . *.java
Package your compiled class files and deployment descriptors into a deployable JAR file using the following command:
jar cvf ShoppingCart.jar com META-INF
Once you have generated the ShoppingCart.jar module, you can deploy this JAR file to WebLogic Server via the Administration Console using the following steps:
Start the Administration Console.
In the left pane of the Administration Console, click to expand the Deployments node.
Click the EJB node. The EJB Deployments table displays in the right pane showing all the EJBs deployed in your domain.
Click the Configure a New EJB text link. A new page displays in the right pane showing the steps to deploy a J2EE application or module (ShoppingCart) to your WebLogic Server.
Step 1 involves ensuring the ShoppingCart.jar file is on the same file system as the Administration Server of your WebLogic domain.
Step 2 involves using the Administration Console to locate the ShoppingCart.jar
file you want to deploy. Once you have located the ShoppingCart.jar
file, use the [select]
link next to that file, as illustrated in Figure 21.4.
Step 3 involves selecting the WebLogic Servers or clusters you want to deploy the ShoppingCart module to and assigning a WebLogic name to the module within the context of the WebLogic environment, as illustrated in Figure 21.5.
Click Configure and Deploy to deploy the ShoppingCart module to your WebLogic Server(s).
Once the ShoppingCart EJB example has been deployed, you will see a Deployment Status By Target screen in the Administration Console indicating the success or failure of the deployment. Figure 21.6 shows a successful deployment of the ShoppingCart module to its target WebLogic Server.
▸ For further information on deployment in the WebLogic Server, see Chapter 27, “Packaging, Assembling and Deploying J2EE Applications,” p. 953.
Once the ShoppingCart module is deployed, the associated tables it uses (BOOKSTORE and SHOPPINGCART) will automatically be created by the EJB container. You can validate the creation of these table using the Catalog option in the Pointbase Console, as illustrated in Figure 21.7.
The ShoppingCart.jar includes the Customer
class which can be used to unit test the API for the ShoppingCart
and BookEntity
EJBs. The Customer
class is run as a standard Java application and uses localhost
as the hostname for JNDI lookups. For this reason it must be run on the same system as the WebLogic Server you deployed the ShoppingCart to.
The source code for the Customer
class was shown in Listing 21.21.
This test program takes four command-line arguments: title, author, price, and bookID. The constructor creates a BookEntity EJB using the command-line arguments as BookEntityVO
data. The main()
method then invokes the purchase()
method which adds the same BookEntityVO()
to a ShoppingCart
. If any exceptions are thrown, the error message is displayed; otherwise a thank you message is displayed for purchasing the book.
To run this test application against the ShoppingCart example:
Open a Command Prompt window and set your Java environment.
Set the CLASSPATH to include the location of the ShoppingCart.jar
, for example using the following statement:
Set CLASSPATH=ShoppingCart.jar;%CLASSPATH%
Run the application using the following command, as shown in Figure 21.8.
java com.objectmind.BookStore.Customer "WebLogic Platform 7.0" "Jatinder Prem" 50.0 1
This chapter described the implementation and deployment of entity EJBs in WebLogic Server. The entity bean provider must be aware of the many services provided by the EJB container. The services include persistence management, transaction management, and life cycle management. Using these services effectively will result in less development time for the bean provider and produce a more reliable, durable, portable, and higher performance in the implementation. There are application-specific issues that require a decision to use bean-managed persistence or container-managed-persistence. With the components of the entity bean are the ejb-jar.xml
and weblogic-ejb-jar.xml
deployment descriptors. If the entity bean is implemented using CMP, a weblogic-cmp-rdbms.jar.xml
persistence descriptor must also be written. The components of the entity EJB are compiled and stored in a jar file along with the deployment descriptors. The entity EJB jar file may be stored in an Entity Application Archive (EAR) file. The JAR or EAR file is then deployed on WebLogic Server to be the persistent storage for an enterprise application.