DekGenius.com
[ Team LiB ] Previous Section Next Section

7.2 Basic JDO Persistence

Most JDO applications fall into the small-to-medium scale. In general, you have a simple to moderately complex domain model built into any one of the following kinds of architectures:

  • Web application

  • Server application

  • Two-tier client/server

The tutorial in Chapter 12 covers the basics of building such an application.

7.2.1 Transaction Management

Though JDO persistence is designed to be transparent to the business component developer, it is not transparent to the application developer. The examples in Chapter 12 show only how to manage persistent objects in the main( ) method of a contrived application. In reality, you will be managing persistent objects through JSPs and servlets, Swing components, and even other persistent objects.

Figure 7-1 shows how JDO might interact with a web application. The JSP views perform queries and display data from the persistent objects. Controllers create, modify, and delete the persistent objects. In other words, the JSP pages perform the same role as the main( ) method from Chapter 12's examples.

Figure 7-1. JDO in a simplistic web application
figs/jdbp_0701.gif

This approach works and you will see no problems from it until your domain model begins growing in complexity. One of the drawbacks of JDO, however, is that it does not manage object relationships automatically—you are responsible for maintaining the integrity of all relationships among persistent objects. When you embed the logic for the creation and deletion of persistent objects in views and controllers, you create a maintenance problem.

For example, your web application could have two controllers: one that deletes an author and another that deletes a book. Each controller would require code that not only calls deletePersistent( ), but that also protects the integrity of the relationship between Author and Book. If the rules governing this relationship change, you need to make sure you find all places in the application where the relationship is being managed and make the appropriate changes.

To diminish the risk of managing relationships in your application, you need to centralize the logic for managing your persistent object relationships. I recommend the creation of an EJB session bean-like class that performs metaoperations on your persistent classes. Example 7-1 shows such a class.

BEST PRACTICE: Centralize the logic for managing object relationships to preserve their integrity and the integrity of the underlying data store.

Example 7-1. A Bookshelf class to manage object relationships
package com.imaginary.ora;
   
import javax.jdo.*;
   
public abstract class Bookshelf {
    static private PersistenceManager getPersistenceManager( ) {
        PersistenceManagerFactory factory;
        Properties props = new Properties( ); 
        
        // load JDO properties
        factory = JDOHelper.getPersistenceManagerFactory(props);
        return factory.getPersistenceManager( );
    }
   
    static public void createAuthor(Author auth) {
        PersistenceManager mgr = getPersistenceManager( );
        Transaction trans;
   
        trans = mgr.currentTransaction( );
        trans.setOptimistic(false);
        try {
            trans.begin( );
            mgr.makePersistent(auth);
            trans.commit( );        
        }
        catch( Exception e ) {
            e.printStackTrace( );
        }
        finally {
            if( trans.isActive( ) ) {
                trans.rollback( );
            }
            mgr.close( );
        }
    }
   
    static public void deleteAuthor(Author auth) {
        PersistenceManager mgr = getPersistenceManager( );
        Transaction trans;
   
        trans = mgr.currentTransaction( );
        try { trans.setOptimistic(true); }
        catch( JDOUnsupportedOptionException e ) { }
        try {
            Iterator books = auth.getBooks( ).iterator( );
   
            trans.begin( );
            while( it.hasNext( ) ) {
                mgr.deletePersistent((Book)it.next( ));
            }
            mgr.deletePersistent(auth);
            trans.commit( );  
        }
        catch( Exception e ) {
            e.printStackTrace( );
        }
        finally {
            if( trans.isActive( ) ) {
                trans.rollback( );
            }
            mgr.close( );
        }
    }
   
    static public void createBook(Author auth, Book book) {
        PersistenceManager mgr = getPersistenceManager( );
        Transaction trans;
   
        trans = mgr.currentTransaction( );
        try { trans.setOptimistic(true); }
        catch( JDOUnsupportedOptionException e ) { }
        try {
            trans.begin( );
            book.setAuthor(auth);
            auth.addBook(book);
            mgr.makePersistent(book);
            trans.commit( );        
        }
        catch( Exception e ) {
            e.printStackTrace( );
        }
        finally {
            if( trans.isActive( ) ) {
                trans.rollback( );
            }
            mgr.close( );
        }
    }
   
    static public void deleteBook(Book book) {
        PersistenceManager mgr = getPersistenceManager( );
        Transaction trans;
   
        trans = mgr.currentTransaction( );
        try { trans.setOptimistic(true); }
        catch( JDOUnsupportedOptionException e ) { }
        try {
            trans.begin( );
            book.getAuthor( ).removeBook(book);
            mgr.deletePersistent(book);
            trans.commit( );        
        }
        catch( Exception e ) {
            e.printStackTrace( );
        }
        finally {
            if( trans.isActive( ) ) {
                trans.rollback( );
            }
            mgr.close( );
        }
    }
}

This class performs two critical tasks:

  • It clearly demarcates the boundaries of transactions involving Author and Book instances.

  • It centralizes all logic relating to the management of Author and Book relationships.

It also has the hidden benefit of making the persistence model transparent to your controller classes. The JSP code to create a new author looks like this:

<% Bookshelf.createAuthor(new Author(firstName, lastName)); %>

This code also uses optimistic transaction management for optimal performance. In doing so, it checks for the possibility that the JDO implementation does not support optimistic transaction management. Not all JDO implementations support optimistic transaction management, and not all transactions are well suited to optimistic transaction management. In general, optimistic transaction management works when you are performing multiple operations and each operation targets a different object.

The exception in this example was the code to create a new Author. Because the operation touched only the Author class for a single operation, it is going to see better performance under data store transaction management.

BEST PRACTICE: Use optimistic transaction management for long transactions involving multiple persistent objects.

7.2.2 Query Control

The previous section made the use of JDO transparent to controllers—views still use JDO queries to retrieve collections of persistent objects. This issue is not the maintenance problem that running creates and deletes in multiple locations produced. It would nevertheless be nice to centralize query logic to provide view pages with the same transparency as well as give us a single location to tweak query logic. The Bookshelf class looks like a good candidate. On the other hand, it probably makes more sense to have a one-to-one association between a persistent class and the class that manages its queries. Example 7-2 shows an AuthorFinder class to handle queries.

Example 7-2. A class that centralizes query logic for Author instances
package com.imaginary.ora;
   
import java.util.*;
   
import javax.jdo.*;
   
public abstract class AuthorFinder {
   
    static private final String GENRE = "gen";
    static private final String YEAR  = "yr";
   
    static public Collection findByGenreYear(String gen, int yr) {
        Extent ext = mgr.getExtent(Author.class, true);
        Query query = mgr.newQuery(ext, 
        "books.contains(book) & (book.year=yr & book.genre = gen)");
        HashMap params = new HashMap( );
   
        query.declareParameters("int yr, String gen");
        query.declareVariables("com.imaginary.ora.Book book");
        params.put(GENRE, gen);
        params.put(YEAR, yr);
        return(Collection)query.executeWithMap(params);
    }
}

This example provides a single query, but in reality it will likely contain a variety of queries to help support various Author searches. This particular query provides the application with a list of all authors who published a book of a specific genre in a specific year. Even though the search has only two parameters, I used executeWithMap( ) because it helps prevent any maintenance ugliness associated with matching parameter order.

BEST PRACTICE: Use executeWithMap( ) when executing multiparameter queries.

    [ Team LiB ] Previous Section Next Section