DekGenius.com
[ Team LiB ] Previous Section Next Section

5.3 The EJB 2.0 CMP Model

EJB 2.0 is a significant, revolutionary improvement over the original EJB specification—especially when it comes to component persistence. Persistence changes include a more solid CMP architecture and a specialized EJB query language (EJBQL) for performing bean searches. Beyond persistence, EJB 2.0 provides for exciting features such as message-driven beans.

5.3.1 Container-Managed Relationships

Under EJB 1.x, container-managed persistence meant the container managed keeping all bean attributes in sync with the data store. This job became problematic for any attributes representing relationships to other beans. The general solution was to store a primary key for the relationship and let clients worry about referencing the other bean.

EJB 2.0 introduces a concept called container-managed relationships, or CMR. A CMR field is a bean attribute that is a relationship to another bean. CMR relationships can take any of the following forms:

One-to-one
One-to-many
Many-to-many

In addition, each relationship can be unidirectional or bidirectional. If, for example, the relationship between Author and Book is unidirectional, I can ask for all books by a specific author but I cannot ask for the author of a specific book. In other words, the Author bean has a reference to its Book instances but the Book bean has no reference to its author. Of course, it would be more appropriate for this particular relationship to be bidirectional, meaning that I can navigate from Author to Book and Book to Author.

BEST PRACTICE: Try to make relationships unidirectional unless absolutely necessary.

5.3.1.1 CMR basics

You identify CMR relationships in your deployment descriptor inside the <relationships> tag. For each relationship, you specify an ejb-relation element:

<ejb-jar>
  ...
  <enterprise-beans>
    <entity>
      <ejb-name>BookEJB</ejb-name>
      <local-home>com.imaginary.ora.BookHome</local-home>
      <local>com.imaginary.ora.Book</local>
      <cmp-version>2.x</cmp-version>
      ...
    </entity>
    <entity>
      <ejb-name>AuthorEJB</ejb-name>
      <local-home>com.imaginary.ora.AuthorHome</local-home>
      <local>com.imaginary.ora.Author</local>
      <cmp-version>2.x</cmp-version>
      ...
    </entity>
  </enterprise-beans>
  <relationships>
    <ejb-relation>
      <ejb-relation-name>Book-Author</ejb-relation-name>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          A Book has an Author
        </ejb-relationship-role-name>
        <multiplicity>Many</multiplicity>
        <cascade-delete/>
        <relationship-role-source>
          <ejb-name>BookEJB</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>author</cmr-field-name>
        </cmr-field>
      </ejb-relationship-role>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          An Author has many Books
        </ejb-relationship-role-name>
        <multiplicity>One</multiplicity>
        <relationship-role-source>
          <ejb-name>AuthorEJB</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>books</cmr-field-name>
        </cmr-field>
      </ejb-relationship-role>
    </ejb-relation>
  </relationships>
  ...
</ejb-jar>

Though for our purposes the bean declarations themselves are not relevant to persistence, I included part of their declarations to show that the relationships section references by the names you declare in the <ejb-name> tags under the enterprise-beans element.

This deployment descriptor says that an author has many books and a book has one author. The Author bean tracks its books via the CMR field books. Similarly, the Book bean tracks its author by the author CMR field.

You must define a pair of accessor methods—a getter and setter—for any CMR fields you declare. You declare these methods as abstract in the bean implementation class. The return type for the getter and parameter for the setter is a Collection for the "many" side of a one-to-many or many-to-many relationship. You can use implementations of Collection to suit the underlying needs of the relationship: TreeSet instances for ordered lists, Set instances for unique lists, and so on. Similarly, the type for the "one" side of a one-to-one or one-to-many relationship is the local interface.[1] The Book interface therefore has these methods:

[1] In addition to remote interfaces, EJB 2.0 added the concept of local interfaces for reference within the container.

public abstract Author getAuthor( );
   
public abstract void setAuthor(Author auth);

Similarly, the Author interface includes:

public abstract Collection getBooks( );
   
public abstract void setBooks(Collection bks);

In practice, you rarely want to set the whole list of books associated with an author at all once. Instead, you probably want to add books as new books are written. You can add other methods such as an addBook( ) method to support these needs. The EJB specification, however, demands that you write at least a getter and setter.

5.3.1.2 CMR magic

The beauty of EJB 2.0 CMR is its ability to manage the referential integrity of these relationships. If you assign one bean's collection of relationships to another bean, that collection is automatically removed from the first bean. If you delete the primary bean in a relationship marked in the descriptor with a <cascade-delete> tag, its dependent beans are also deleted.[2] In the sample descriptor, a Book will be deleted whenever its Author is deleted from the database to prevent us from having books with no authors.

[2] This feature is also known as a cascade delete.

5.3.2 EJB QL

Besides entity relationships, EJB 1.x CMP also had difficulties with searches. The main problem was that you had to write a different finder method to support every way you could possibly search for a bean or set of beans. Furthermore, the semantics of these finder methods left a lot to interpretation. For example, everyone knows what getTitle( ) and setTitle( ) do. Do you know what findBooksByYear( ) does? On the face of it, you would think it would provide all books published in a specific year. However, it can also mean find all books published after a specific year or before a specific year or even by authors born in a specific year. A container simply has no way to know from the EJB 1.x semantics.

5.3.2.1 Finders

EJB 2.0 introduced a query language called the EJBQL to address these searching issues. The best way to think of EJBQL is as SQL for EJBs. In other words, as SQL enables you to query tables in a relational database, EJBQL lets you query beans in your application.

You define EJBQL queries inside your deployment descriptor:

<ejb-jar>
  <enterprise-beans>
    <entity>
      <ejb-name>BookEJB</ejb-name>
      <local-home>com.imaginary.ora.BookHome</local-home>
      <local>com.imaginary.ora.Book</local>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>Book</abstract-schema-name>
      <primkey-field>bookID</primkey-field>
      <cmp-field><field-name>title</field-name></cmp-field>
      <query>
        <query-method>
          <method-name>findByTitle</method-name>
          <method-params>
            <method-param>java.lang.String</method-param>
          </method-params>
        </query-method>
        <ejb-ql>
          SELECT OBJECT(b) FROM Book b WHERE b.title = ?1
        </ejb-ql>
      </query>
    </entity>
    ...
  </enterprise-beans>
  ...
</ejb-jar>

The <query> tag introduces a query that applications can perform on the bean. It has two parts. The first part—<query-method>—describes the external interface, a finder method called findByTitle(String). The second part—<ejb-ql>—describes the actual query. It looks a lot like SQL but is different enough to make you tilt your head sideways.

The key to the EJB QL is the bean's abstract schema name. When you construct an EJB QL statement, you reference beans by their abstract schema names. In this example, the Book bean has the abstract schema name of Book. Thus, any time a book is referenced, it is referenced through the Book abstract schema name. Naturally, no two beans can share the same abstract schema name.

Two other pieces to the query are very much un-SQL. The first piece is the superfluous function OBJECT( ). It is supposedly an indicator to the container that you expect an EJB reference for that value. Of course, since you specified it as a Book, that desire is rather obvious. Just humor the container and use OBJECT( ) wherever you are referencing an EJB.

The last piece is the ?1 token. This token parallels JDBC prepared statement tokens. The major difference is that you add a parameter number after the ?. Thus ?1 represents the first parameter to the findByXXX( ) method, ?2 the second, and so on.

For the most part, the rest of EJB QL continues to look a lot like SQL—except with less functionality. It is missing many of the functions such as MAX( ) that are basic to SQL.[3] Furthermore, it has no concept of ordering. A full discussion of EJB QL's syntax is beyond the scope of this book.

[3] Some containers provide container-specific implementations of these missing functions.

You can define a finder method in your home interface to return single instances or collections. It will not matter to the container. It will generate the appropriate code for your finder based on the return type you specify in the interface's declaration for the finder.

5.3.2.2 Selectors

The audience for finders is external clients and other beans. EJB 2.0 also introduced a brand new class of query methods called selectors. Selectors support internal queries by a bean. In other words, you cannot call a selector externally.

Inside your bean implementation class, you provide abstract declarations for any selectors you desire:

public abstract Author ejbSelectWithBook( )
  throws FinderException;

This query—which could appear in any bean implementation class that needs it—identifies the author with the specified book. It is accompanied by a deployment descriptor with the following <query> tag:

<query>
  <query-method>
    <method-name>ejbSelectWithBook</method-name>
    <method-params>
      <method-param>com.imaginary.ora.Book</method-param>
    </method-params>
  </query-method>
  <ejb-ql>
    SELECT OBJECT(a) FROM Author a IN (a.books) bk WHERE bk = ?1
  </ejb-ql>
</query>

In addition to being another way to query for beans, selectors also enable you to query for data from beans:

SELECT b.title FROM Book b WHERE b.title LIKE '%rain%'

This code provides the titles of all books as a Collection of String instances for the books that have the substring "rain" in their titles.

You cannot use query parameters in EJBQL LIKE comparisons.


Selectors and finders are very similar animals. As you can see from the ability of selectors to return partial bean data, however, selectors have slightly richer capabilities. The huge difference is their visibility. Selectors are not part of the remote or local bean interfaces; finders are. Finders are meant to enable external components to find beans matching some set of criteria. Selectors, on the other hand, are meant to enable a bean to perform its own arbitrary searches.

    [ Team LiB ] Previous Section Next Section