Saturday, December 19, 2009

5.2 The EJB 1.0 CMP Model



[ Team LiB ]






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

Class



Attribute



Type



Manageable by EJB 1.x CMP?



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:



  • A record is inserted into the database or other persistent store
    between the two calls.

  • The association between the bean and the EJB object is established
    between the two calls.




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.








    [ Team LiB ]



    No comments: