ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: Java Database Best Practices

JDO Persistence, Part 2

Related Reading

Java Database Best Practices
By George Reese

by George Reese

Editor's note: In part one in this three-part series on JDO persistence, excerpted from Java Database Best Practices, George Reese described all of the available persistence options for Java architects and developers. In part two this week, George covers JDO persistence best practices for transaction management and query control.

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:

The tutorial in Chapter 12 ("TITLE") covers the basics of building such an application.

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

JDO in a simplistic web application

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 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.

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.

Next week, in the third and final installment in this series of excerpts on JDO Persistence, George Reese will cover persisting EJBs as part of a bean-managed persistence.

George Reese is the founder of two Minneapolis-based companies, enStratus Networks LLC (maker of high-end cloud infrastructure management tools) and Valtira LLC (maker of the Valtira Online Marketing Platform). He is also the author of technology books such as the MySQL Pocket Reference, Database Programming with JDBC and Java, and Java Database Best Practices.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.