Last summer, Sun Microsystems released the first public draft of the EJB 2.0 specification with a lot of fanfare. Since then, it's been through a whirlwind of discussion, controversy, and modifications. Yes, modifications. The latest release of the EJB specification is Public Final Draft 2, which was released at the end of April.
The latest incarnation of the EJB specification has a variety of features that developers should become familiar with:
This article provides a rapid-fire implementation primer to the new features of the EJB 2.0 specification for developers with prior EJB experience.
- The introduction of message-driven beans
- The creation of a new entity EJB container-managed persistence model
- A model for creating container-managed relationships between entity EJBs
- The creation of a standard query language, EJB-QL, for querying EJBs and their properties
- The introduction of local interfaces for session and entity beans
- The renaming of remote interfaces to component interfaces
- The introduction of home methods
- The introduction of ejbSelect methods that allow an entity EJB to internally query for properties using a deployment descriptor-defined EJB-QL query
The examples in this article are based on the simple UML diagram presented in Figure 1. Our mini e-business system will model components that might be used by a magazine publisher. Our magazine component will be a CMP-entity EJB that can have zero or more article CMP-entity EJB components in a one-to-many container-managed relationship (CMR). The client application will be able to query the magazine component for magazines and articles. Additionally, the client application will be able to register a subscriber to a magazine by sending a JMS message to a queue that has a registration message-driven bean consumer. The registration message-driven bean will simulate registering the subscriber and sending an e-mail message to confirm the information.
The examples provided with this article are configured to run on BEA WebLogic Server 6.0, Service Pack 1. The examples are based on the EJB 2.0 specification, Public Draft 1 (PD1). The examples do not support PD2 semantics since a vendor implementation supporting PD2 did not exist at the time this article was written. PD2 introduces requirements around the use of local interfaces for entity EJBs participating in a CMR relationship, a modified EJB-QL language, and a different DTD for the EJB deployment descriptor.
Message-driven beans are a new component type in the EJB 2.0 specification that allows developers to meld JMS with a component model. A message-driven bean is an EJB component that's a JMS consumer. Message-driven beans have all of the benefits of decoupled, asynchronous message-based models. For a good description of the benefits of using asynchronous invocations, check out the "Message-Driven Beans" chapter in the upcoming Mastering EJB II book that's undergoing community review at theserverside.com. Once the chapter has completed community review and is published, it will be made available for download in a PDF format.
Writing a message-driven bean is straightforward. Message-driven beans do not have client-visible home or component interfaces since they're decoupled from message producers. Thus, a message-driven bean's implementation is a class that:
Listing 1 shows the implementation of a registration bean. (All listings appear below.) It contains comments explaining the bean developer's responsibility for each message-driven bean method implementation. In the registration bean, the onMessage(...) method expects to consume a message of type MapMessage. The MapMessage will have two string instances contained within it: one with the name of the magazine that should be subscribed to and another with the e-mail address of the individual that wants the subscription.
- Implements the javax.ejb.MessageDrivenBean interface (ejbRemove() and setMessageDrivenContext(...))
- Implements the javax.jms.MessageListener interface (onMessage(...))
- Provides an ejbCreate() implementation
The examples provided in this article contain a client test application, Client.java (not listed). This application tests all aspects of the EJBs deployed for this article. In particular, for the registration bean, the client application creates a transacted QueueSender instance, fills a MapMessage with the appropriate values, and sends the message to the SampleQueue deployed in the application server. The registration bean's deployment descriptor contains the name of the JMS destination that the bean subscribes to.
Creating a deployment descriptor for a message-driven bean is equally simple. The DTD for EJBs has been extended to include a <message-driven> tag that a bean developer adds when the bean is first built. A message-driven bean is required to declare:
Note: For message-driven beans that use container-managed transactions, the valid transaction attributes are Required and NotSupported. If the message-driven bean container is configured to use the Required attribute, message acknowledgment will be performed when the user transaction commits or denies when it rolls back. If the message-driven bean container is configured to use the NotSupported attribute, message acknowledgment is undefined in the specification but will likely happen after the completion of the onMessage(...) method.
- The recommended JMS destination type for this bean. The recommended destination type can be javax.jms.Queue or javax.jms.Topic. These are recommendations made by the bean developer to the deployer indicating the type of destination this bean should consume messages from. This is declared with the <destination-type> tag. Note: the actual destination that a message-driven bean binds to is not listed in the ejb-jar.xml file. The destination that a message-driven bean binds to is application server-dependent and included in a vendor implementation deployment descriptor.
- If the recommended JMS destination type for a bean is javax.jms.Topic, the bean developer can configure the message-driven bean to be a durable subscriber of messages. This allows a message-driven bean container to crash and later consume any missed messages. This is declared with the <subscription-durability> tag.
- An optional message selector to be used by the JMS consumer to filter out messages of interest. This is declared with the <message-selector> tag.
- An optional <acknowledge-mode> tag that tells a container using bean-managed transactions the semantics that should be used to determine when a consumed message should be acknowledged to the JMS server. The valid acknowledgment values are AUTO_ACKNOWLEDGE and DUPS_OK_ACKNOWLEDGE and have the same semantic behavior as listed in the JMS 1.0.2 specification.
Listing 2 contains the elements for all of the EJBs in this article. The <message-driven> tag declares the registration bean.
New CMP Model
The EJB 2.0 specification introduces a new format for handling container-managed persistence. It is commonly agreed that this new format is more flexible and easier to develop with than the EJB 1.1 model.
The specification provides a formal mechanism for declaring properties that should have their persistence managed by the container. This involves the bean developer altering his or her approach to developing an entity bean that will be managed through container-managed persistence. In particular, bean developers must now:
Listings 3-5 contain the implementation of a simple CMP entity EJB. The EJB has three CMP properties: articleID (primary key), text, and author. The implementation class declares each of these properties using the appropriate abstract accessor methods. Additionally, the class is declared to be abstract.
- Declare each property through a series of get/set accessor methods that are declared abstract. The data type of the property can be determined by a container generation utility by using introspection on the return type of the getXXX() method and the input parameter of the setXXX(...) method. Declaring CMP properties in the bean class is less ambiguous now since developers need only to provide a pair of JavaBean-like accessor methods in the implementation class.
- Declare the implementation class to be abstract. The application-server vendor will provide the concrete bean implementation class during container generation. The container generation utility will extend this abstract class with an implementation of its own that provides a concrete implementation for all of the abstract property accessor methods. The actual persistence logic for the EJB will be located in the concrete implementation created by the container generator.
- For each property declared with an abstract accessor method in the implementation class, the bean developer must provide a matching <cmp-field> declaration in the deployment descriptor. The value of the <cmp-field> tag must start with a lowercase letter and match the text following the get/set accessor methods in the implementation class. For example, if you declare <cmp-field>age</cmp-field>, the bean implementation class must have equivalent getAge() and setAge(...) methods.
- The primary key must be a CMP field in the bean implementation class with an appropriate <cmp-field> declaration in the deployment descriptor. Additionally, the <primkey-field> tag must be used to declare which <cmp-field> value is the primary key value for the EJB. Complex primary keys are still supported and have a slightly different semantic associated with them.
- The ejbCreate(...) method must return null. The concrete implementation will create an appropriate return value that will be handled by the container.
It's extremely important to understand that the container callback methods and the business logic implementation methods must use the abstract accessor methods to read or write any of the CMP properties. This is demonstrated in the ejbCreate(...) method where the properties of the bean are initialized using the appropriate set methods. Also note that the ejbCreate(...) method returns null as defined by the specification.
Listing 2 contains the deployment descriptor for the article EJB. The declaration of the <cmp-field> and <primkey-field> tags are done for the article EJB as described by the rules listed above. You might be wondering what the intent of the <abstract-schema-name> tag is. The value of this tag will be used in any EJB-QL queries that are defined for your EJB. Its value acts as a symbolic data type that references this particular EJB in the query. Since multiple EJBs can be declared in a single deployment descriptor, it's required that each EJB has a unique <abstract-schema-name> value.
Because of the innovative approach taken to declaring CMP properties of an entity EJB, application server vendors can provide a whole series of enhancements to increase performance of entity EJBs:
- Lazy loading of fields when they're accessed: The container can determine when a get method is invoked and load fields at the time of the invocation instead of pre-fetching values.
- Group loading of fields: An application server vendor can provide a way to declare groups of fields so that when one field is accessed, all of the fields for the group will be loaded as part of a single query.
- Optimized writes to a database: Since the container can monitor all get-and-set method invocations made, the container can optimize any write queries to write out only those fields that were modified, if any.
- The creation of read-only and read-write distributed data caches: These can be done at the container level in a much more automated way since properties can be monitored for alterations.
New EJB Query Language
Because of the declaration of CMP fields in a deployment descriptor, it's now possible to create queries in the deployment descriptor for any finder method other than findByPrimaryKey(). findByPrimaryKey()'s query is automated by the container. Queries are declared in the ejb-jar.xml deployment descriptor as part of the <query> tag. The <query> tag declares the method that the query belongs to, and the query itself.
EJB-QL queries can also be applied to ejbSelect() methods, which are new in the EJB 2.0 specification. ejbSelect() methods are private methods declared in an implementation class, and used by other implementation methods to access fields and collections of fields that are accessible from a particular EJB. This includes fields that are accessible through a container-managed relationship. Even though we do not provide an ejbSelect() implementation in our example, the definition of the EJB-QL query is similar to those for finder methods.
EJB-QL queries have three clauses: SELECT, FROM, and WHERE. The SELECT clause is needed for ejbSelect() method queries but can be dropped for finder queries. For example, Listing 6 contains the home interface for the magazine entity EJB that uses CMP for its name and editor fields and participates in a container-managed unidirectional, one-to-many relationship with article. Listing 2 contains an EJB-QL query for the findAllMagazines() method declared in the home interface. We want this method to return references to any magazine that exists in the persistent store.
Let's break down the value, <![CDATA[FROM MagazineBean mb WHERE mb.name IS NOT NULL]]>:
This example will return references to all magazine EJBs whose name CMP field is not null.
- <![CDATA[ ... ]]>: This construct is used to escape any SGML tags that may appear within the query. The body of this declaration is the query.
- FROM MagazineBean mb: This declares a query variable, mb, of type MagazineBean. MagazineBean comes from the <abstract-schema-type> declaration of the EJB as mentioned above. The mb variable can be used to reference an instance of that particular EJB type in the WHERE clause of the query.
- WHERE mb.name IS NOT NULL: This clarifies the query. The dot '.' syntax is used to navigate to any CMP field or container-managed relationship. mb.name refers to the value of the name CMP field of the mb EJB instance. The dot '.' syntax can go many levels deep, but can not be used to navigate to a field or a relationship that references a collection of fields. A different syntax must be used.
New Container-Managed Relationship Model
Container-managed relationships (CMR) are easy to define using the new EJB 2.0 model. CMRs can be one-to-one, one-to-many, or many-to-many. Additionally, CMRs can be unidirectional or bidirectional. The bean developer is responsible for defining the relationships between beans in the implementation classes and the deployment descriptor.
The bean developer has to perform the following activities to define a CMR:
Listing 2 contains the <relationships> tag for the magazine-article CMR. Notice that only the MagazineEJB source has a <cmr-field> declared since we're creating a unidirectional relationship. Additionally, the value of the <cmr-field> must match the text appearing after get/set in the accessor method.
- Provide abstract accessor methods that indicate directionality and multiplicity in the implementation class. Similar to CMP field declaration, every CMR field must have a get/set method that references the other EJB in the relationship.
- If the target of the relationship has a multiplicity of one, then the get/set methods must reference the remote interface of the other EJB in the relationship. Note: In PD2, the specification requires accessor methods to reference only the local interface of the other EJB in the relationship.
- For relationships where the target of the relationship has a many multiplicity, the get/set accessor methods representing the relationship must use collection or set as their data type. Listings 6-8 contain a one-to-many, unidirectional relationship to the article EJB. The magazine EJB implementation class declares Collection getArticles() and setArticles(Collection) since the target of the relationship is a many multiplicity.
- The absence of get/set accessor methods in an implementation class indicates lack of directionality for that particular EJB. As a result, the article EJB implementation (Listing 5) does not have the get/set accessor methods to the magazine since this relationship is unidirectional.
- The source EJB, target EJB, directionality, and multiplicity of the CMR are also defined in the deployment descriptor via a <relationships> tag that appears after all EJB declarations, but before the <assembly-descriptor>. The <ejb-relation> tag defines a single relationship between two EJBs. An <ejb-relation> tag has two <ejb-relationship-role> tags that define each end of the relationship. Each <ejb-relationship-role> tag defines the <multiplicity> for this role, the EJB that is the <role-source> of this role, and a <cmr-field> that indicates whether or not this EJB contains get/set accessor methods that implement directionality for this source.
It's important to understand that the actual persistent mapping of the relationship is not defined anywhere in the standard deployment descriptor; it is completely vendor-dependent. For example, the most common technique used to represent a one-to-many relationship in a relational model is to have a foreign key in one table pointing to a primary key in another. The foreign key would reside in the table representing the "many" multiplicity of the relationship and would point to the primary key of the table representing the "one" multiplicity. This abstract-to-concrete persistence mapping is not defined in the EJB specification and is left up to each EJB container vendor to supply. For BEA WebLogic Server, this is done in the weblogic-ejb-jar.xml and weblogic-cmp-rdbms-jar.xml files.
As a final note, it should be understood that EJB-QL is robust enough to be able to navigate CMRs. For example, the magazine EJB home-interface defined a findMagazinesWithArticlesBy(String) method that requires an EJB-QL query that navigates magazine and article. Since the dot '.' syntax can not be used to navigate relationships with a multiplicity of many, a special syntax has been created to support this. The query provided is FROM MagazineBean mb, article IN mb.articles WHERE article.author = ?1. Some points about this query include:
- The IN clause defines a loop variable that refers to a single instance defined in a collection. article IN mb.articles declares a local loop variable, article, that refers to a single instance of the collection of article references that are associated with this magazine instance as defined by the CMR field, articles.
- Since the article variable refers to a single instance of the collection, article can be used in the WHERE clause to reference CMP fields of the article EJB.
- ?1 is EJB-QL syntax for referencing the first input parameter of the finder method. ?2 would represent the value of the second input parameter, etc.
- The syntax for this example is based on PD1 and has changed slightly in PD2.
Whew! What a mouthful! As you can see, the EJB 2.0 specification made many modifications that impact EJB developers. This rapid-fire article attempted to provide a quick introduction to many of the issues that developers will face immediately. Now, it's time to go have some fun and implement the biggest and brightest e-business systems using your newfound EJB knowledge!
Tyler Jewell is a principal technology
evangelist for BEA Systems, and an expert educator, mentor, and
lecturer on enterprise technologies. He is a coauthor of Mastering EJB 2.0 (Wiley) and a
contributor to Pure EJB (SAMS). He is also a member of the O'Reilly Network's ONJava.com editorial advisory panel.
Download Assoicated Source Files (Zip format ~ 342 KB)