5.2 The EJB 1.0 CMP Model
EJB 1.x CMP generates the code to map persistent entity bean fields to columns in a relational database. Under this model, you can map only Java primitives and serializable fields. You cannot, for the most part, map entity beans or any other nonserializable Java object. Table 5-1 shows the attributes of two analysis-level business objects: an Author class and a Book class.
Table 5-1. The attributes of two business objects and EJB 1.x CMP's ability to manage them|
Author
|
authorID
|
long
|
Yes
| |
firstName
|
String
|
Yes
| |
lastName
|
String
|
Yes
|
Book
|
bookID
|
long
|
Yes
| |
author
|
Author
|
No
| |
title
|
String
|
Yes
|
EJB 1.1 introduced the ability to map entity bean references to the database. The actual implementation of this support, unfortunately, varies from container to container. As a general rule, you should store the primary keys of any entity bean references and let the request object map it to the entity reference. The Book bean in Table 5-1, for example, could have an authorID field instead of an author field. Example 5-1 shows this approach.
Example 5-1. A container-managed Book bean that references an Author bean
package book; import javax.ejb.EntityBean; import javax.ejb.EntityContext; public class BookEntity implements EntityBean { public Long authorID;
public Long bookID; public EntityContext context; public String title; public void ejbActivate( ) { } public Long ejbCreate(Long bid, Long aid, String ttl) { bookID = bid; authorID = aid; title= ttl; return null; } public void ejbLoad( ) { } public void ejbPassivate( ) { } public void ejbPostCreate(Long bid, Long aid, String ttl) { } public void ejbRemove( ) { } public void ejbStore( ) { } public Long getAuthorID( ) { return authorID; } public Long getBookID( ) { return bookID; } public String getTitle( ) { return title; } public void setEntityContext(EntityContext ctx) { context = ctx; } public void unsetEntityContext( ) { context = null; } }
I will discuss the details of this class later. If I have code that needs the author of the book, I can get the authorID and then request the Author by its primary key:
public Author getBookAuthor(Book book) throws Exception { Context ctx = new InitialContext( ); Object ref = ctx.lookup("java:comp/env/Author"); AuthorHome home = (AuthorHome)PortableRemoteObject.narrow(ref, AuthorHome.class); return home.findByPrimaryKey(book.getAuthorID( )); }
This approach has two virtues. First, it avoids the lack of ability in EJB 1.x CMP to map between entities. Second, it creates the same effect as lazy-loading by loading the Author only when a client actually needs access to it.
5.2.1 Field Mapping
One of the weaknesses in the EJB 1.x CMP model lies in how it maps attributes to the database. Each container provides its own mechanism for defining the mapping between an entity bean and a table. In most cases, you can use a GUI tool that enables you to relate a bean attribute to a relational column. If you do not care at all about the underlying data model, you can simply write up an XML deployment descriptor that enumerates the entity attributes to be mapped. Example 5-2 shows a sample deployment descriptor.
Example 5-2. A sample EJB deployment descriptor
<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.2//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_2.dtd"> <ejb-jar> <description> </description> <enterprise-beans> <entity> <description> </description> <ejb-name>BookBean</ejb-name> <home>book.BookHome</home> <remote>book.Book</remote> <ejb-class>book.BookEntity</ejb-class> <primkey-class>java.lang.Long</primkey-class> <reentrant>False</reentrant> <persistence-type>Container</persistence-type> <cmp-field><field-name>bookID</field-name></cmp-field> <cmp-field><field-name>title</field-name></cmp-field> <cmp-field><field-name>authorID</field-name></cmp-field> <primkey-field>bookID</primkey-field> </entity> <entity> <description> </description> <ejb-name>AuthorBean</ejb-name> <home>book.AuthorHome</home> <remote>book.Author</remote> <ejb-class>book.AuthorEntity</ejb-class> <primkey-class>java.lang.Long</primkey-class> <reentrant>False</reentrant> <persistence-type>Container</persistence-type> <cmp-field><field-name>authorID</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <primkey-field>authorID</primkey-field> </entity> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>BookBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>AuthorBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
This deployment descriptor tells your container that you have created an entity bean with the specified home, remote, and implementation classes. The fields in the <cmp-field> tags identify which BookEntity attributes should persist to your database.
If you deploy this simplistic application on an Orion Server, it will create a BookBean table in your database and set up bookID, title, and authorID columns for you with bookID established as the primary key. You do not have to write any JDBC code. You do not have to write any SQL. You do not have to create any tables.
If you have a data model constructed already, you will need to use the proprietary tools that come with your J2EE container to map the bean to the data model. In the case of Orion Server, for example, you can create an orion-ejb-jar.xml file to handle custom mappings. Most application servers provide a GUI to help identify the mappings.
5.2.2 Persistence Methods
If you look back to Example 5-1, you will notice that most of the methods required for an EJB entity bean are empty. They are empty because the container is handling the persistence operations for you. The persistence operations that generally contain code under a CMP model are the ejbCreate( ) and ejbPostCreate( ) methods.
The container passes both methods the values specified by the client. It calls ejbCreate( ) before persisting the bean to the database and ejbPostCreate( ) afterward. Assuming you have no business logic associated with object creation, the responsibility of ejbCreate( ) is to assign the initial values to the bean:
public Long ejbCreate(Long bid, Long aid, String ttl) { bookID = bid; authorID = aid; title= ttl; return null; }
The return value has no meaning under CMP. You should therefore return null as I did in the BookEntity class.
You must make sure you assign the primary key value in all ejbCreate( ) methods. You consequently cannot rely on any underlying database tools, such as the MySQL AUTO_INCREMENT feature, to generate primary keys. At the end of Chapter 4, I introduced a database-independent approach to generating unique values. This approach uses a Sequencer object to generate unique long values. The AuthorEntity class takes advantage of the Sequencer:
public Long ejbCreate(String fn, String ln) throws CreateException { try { Sequencer seq = Sequencer.getInstance(AUTHOR_ID); authorID = new Long(seq.next( )); } catch( PersistenceException e ) { throw new CreateException(e.getMessage( )); } firstName = fn; lastName = ln; return null; }
Instead of requiring the client to worry about primary key generation, the burden has been placed where it belongs梚nside the creation logic for the bean.
|
BEST PRACTICE: Perform primary key generation in your CMP EJB instead of the client.
|
|
The container calls ejbPostCreate( ) once the bean is established as a persistent entity to enable you to perform any extra initialization. The state differs at the time of ejbCreate( ) in two significant ways:
5.2.3 Searches
One of the great weaknesses of EJB 1.x CMP is its poor support for searching on anything but the primary key. To search for a book with a specific title, for example, you would add a finder method to your home interface:
Collection findByTitle(String ttl) throws RemoteException, FinderException;
The good news is that you do not have to code anything other than the basic search signature in the home interface. The bad news is that there is no container-neutral method for telling an EJB container exactly what findByTitle( ) is supposed to do. Is it supposed to look for exact matches? Is the ttl value being passed a regular expression? The only thing a container knows from a finder method signature is whether that method is expected to find a unique value or multiple hits. The EJB 1.x specifications say nothing about how a container is supposed to identify what you mean to search for. As a result, you must rely on proprietary deployment tools to assist the container in defining your EJB finder methods.
5.2.4 Transactions
Without being flip, I believe it is safe to say that if you need complex transactions you probably should not be using EJB 1.x CMP梱ou should instead be using 2.x CMP or BMP. You describe the level of transaction support your beans need in the EJB deployment descriptor. The deployment descriptor in Example 5-2 had no transaction support. Indeed, this simple application needed no transaction support.
EJB lets you specify the transaction characteristics of beans on a method-by-method basis as part of the deployment descriptor. The deployment descriptor approach is called declarative transaction management. Declarative transaction management is a huge advantage of all EJB persistence models. Whereas most other models require the programmer to manage transaction semantics in code梩he hardest part of database programming桬JB does not require the programmer to understand anything about transaction semantics. Only the deployer needs to worry about such things, and the deployer can tweak the transactional attributes of the system without modifying and recompiling code.
|
If you are unfamiliar with the basic concepts of transaction management, now is an excellent time to go back and read Chapter 3.
|
|
When you deploy a bean梕ntity or session梱ou assign it a transactional attribute in the deployment descriptor. The transactional attribute tells the container how to handle transactions for that bean. You can even assign different transactional attributes to different methods within the bean if you desire that level of control. The transactional attributes are:
- NotSupported
No transaction scope is propagated to the method. In other words, if this method is called in the middle of another transaction, that transaction is suspended to allow this method to execute. No transactional context exists for any calls made within this method. Once this method completes, the original transaction resumes.
- Supports
This attribute says that the method in question will act with whatever transaction scope it is executed in. If not called in an existing transactional scope, it will not attempt to create one.
- Required
A method with a Required attribute must be executed in a transactional context. If not called in an existing transactional context, it will initiate a new one.
- RequiresNew
No matter what context in which the method in question is called, this method will create a new transaction context. It will suspend any existing transactional context until it completes.
- Mandatory
The method in question can never initiate its own transactional context梚t must always occur inside a caller's transactional context. If it is called without any transactional context, the container will throw an exception.
- Never
The opposite of Mandatory, Never means that the method in question can never be called inside a transactional context. If it is, the container will throw an exception.
The transactional attributes in Example 5-2 were specified in the following lines:
<container-transaction> <method> <ejb-name>BookBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>AuthorBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction>
This descriptor states that transactions for all methods in both beans are not supported. If for some reason a client calls them within a transactional context, that context will be suspended until the methods in these beans complete.
The <container-transaction> tag associates a method with a transactional attribute. Inside the <method> tag, I use * for the method name to indicate that the attribute applies to all methods in the specified bean. The <trans-attribute> tag is the tag that identifies which transactional attribute to assign. You can override a * value by naming a specific method in a later <container-transaction> tag.
I have just touched on the basics of transaction management in the EJB 1.x CMP persistence model. Much of the detail, however, applies both to the BMP and EJB 2.x CMP persistence models, which are covered in the next section and later in this chapter.
|
No comments:
Post a Comment