DekGenius.com
[ Team LiB ] Previous Section Next Section

12.3 Queries

Before you do anything else to your data store of books, you need a way to get them out of the data store. JDO provides a unique combination of a query API combined with a simplistic query language to enable access to objects in your data store.

12.3.1 The JDO Extent

JDO makes heavy use of a concept called an extent. An extent is like a virtual collection of persistent objects. By virtual, I mean that all the objects represented by the extent are probably not loaded into memory. A regular Java collection, on the other hand, has all elements of the collection in memory. The abstraction of the extent is critical to JDO programming since it performs important optimization tasks like lazy-loading while hiding these complexities from your application.

The simplest use of an extent is as a representation for all instances of a particular persistent class. The following code provides your application with all books in the data store:

Extent ext = mgr.getExtent(Book.class, true);
Iterator books = ext.iterator( );
   
while( books.hasNext( ) ) {
    Book b = (Book)books.next( );
   
    System.out.println("Got: " + b.getTitle( ));
}
ext.closeAll( );

Just as the application worked through the PersistenceManager to create new books, it also works through the PersistenceManager to get an extent representing existing books. The second parameter—a boolean value—indicates whether subclasses should be included in the extent. In this example, we indicated that we want subclasses even though we know Book has no subclasses. The application takes an Iterator from the extent and goes through each one, printing its title.

12.3.2 The JDO Query

It is rare that you will want the whole list of objects of a certain class. For most applications, you will want to filter that list based on some set of criteria. The JDO query API provides a key object to enable complex queries, the Query. The following code provides all books by Anne Rice:

Extent ext = mgr.getExtent(Book.class, true);
Query query = mgr.newQuery(ext, "author=  =name");
Collection results;
   
query.declareParameters("String name");
results = (Collection)query.execute("Anne Rice");

Using your PersistenceManager, you create a query based on an Extent representing all books and a filter for elements in that Extent. In this case, the filter is based on the author field. It tells the query to look for all books for which the author equals name. The name token is just a placeholder. In the call to declareParameters( ), we tell the query that name is a parameter to be passed in and that it will be a String. Finally, we get all matching books by calling execute( ) in the query with a specific author name—in this case, Anne Rice.

You can specify up to three parameters to any query execution using the preceding syntax. They will be matched to your parameter declarations in declareParameters( ) based on order. If you need more than three parameters, you can use the executeWithArray( ) method instead of execute( ):

Extent ext = mgr.getExtent(Person.class, true);
Query query = mgr.newQuery(ext, 
  "lastName=  =last & city=  =cty &state=  =st  & birthCity =  = bc");
Object[  ] params = { "Allen", "Boston", "MA", "Miami" };
Collection results;
   
query.declareParameters("String last, String cty, " +
  "String st, String bc");
results = (Collection)query.executeWithArray(params);

This uninteresting query returns all people in your data store with the last name Allen who live in Boston but were born in Miami.

Whatever syntax you use, the parameter passed should be an object type—the JDO implementation will unwrap into primitives when appropriate. The best way to execute a multiparameter statement, however, is through the executeWithMap( ) method:

Extent ext = mgr.getExtent(Person.class, true);
Query query = mgr.newQuery(ext, 
  "lastName=  =last & city=  =cty &state=  =st  & birthCity =  = bc");
Collection results;
HashMap params;
   
query.declareParameters("String last, String cty, " +
  "String st, String bc");
params = new HashMap( );
params.put("last", "Allen");
params.put("cty", "Boston");
params.put("st", "MA");
pparams.put("bc", "Miami");
results = (Collection)query.executeWithMap(params);

When you use a map, you make your Java code independent of the order specified in your query. You can therefore make changes to the order of the parameters in the query without having to make any changes to the Java code. If you had used an array, you would have to make sure the parameters were placed into the array in the proper order.

12.3.3 Complex Queries

JDO supports many different ways for performing queries depending on your application needs. In an application context, for example, it is common to ask which objects in a subset of objects meet certain criteria. To facilitate these kinds of queries, JDO enables you to operate on collections as well as extents. The following query provides all of the books by a particular author published in 1991:

Query query = mgr.newQuery(Book.class, author.getBooks( ),
                            "year=  =yr");
Collection results;
   
query.declareParameters("int yr");
results = (Collection)query.execute(new Integer(1991));

The code is nearly identical to everything you have seen so far. The key difference is the use of a collection in constructing the query instead of an extent. In this case, author.getBooks( ) is a call to get all associated books from an instance of the Author class representing a specific author. Those books form the set of objects on which the query operates.

In addition to parameters, JDO also enables you to declare variables to be used in filtering. This feature is useful when you want to operate on individual elements in a collection field. If, for example, we wanted all authors who had written for a particular genre, we might execute against the database using the following SQL join:

SELECT Author.authorID
FROM Author, Book
WHERE Author.authorID = Book.bookID
AND Book.genre = "some genre";

JDO does things differently. As before, you start with the Author extent. This time, however, you create a variable that will represent each of an author's books and compare that book's genre to the target genre:

Extent ext = mgr.getExtent(Author.class, true);
Query query = mgr.newQuery(ext, 
  "books.contains(book) & book.genre =  =genre");
Collection results;
   
query.declareParameters("String genre");
query.declareVariables("com.imaginary.ora.Book book");
results = (Collection)query.execute("HORROR");

This critical aspect of variable usage is the contains( ) call. In truth, it is a rather unintuitive syntactic construct. It is saying that for each book found in the author's list of books (books), that book will be assigned to the book variable and used in the comparison book.genre = = genre.

Unlike parameters, multiple variable declarations are separated by semicolons (;).


The JDO specification requires that your contains( ) clause be on the lefthand side of any & expression. Furthermore, each part of a | expression must have a contains( ) clause.

Finally, you can order your results using the setOrdering( ) method in query:

query.setOrdering("lastName descending, firstName descending");

As you would probably expect, this call tells the query to sort on lastName followed by firstName. You can sort on fields of any primitive type except boolean. In addition, you can sort on fields of a wrapper type (except Boolean), BigDecimal, BigInteger, String, and Date.

12.3.4 The Filter Language

I have danced around the issue of describing the filtering syntax until now. In general, it is fairly straightforward. It looks and acts a lot like Java. In fact, the only oddity so far has been the use of a single & for "and". As a whole, the filter language follows Java syntaxes with some largely natural exceptions. The exceptions include:

  • The filter syntax provides the ability to compare and operate on primitives and wrappers together.

  • Objects provide access to only a few methods such as String.startsWith( ), String.endsWith( ), Collection.contains( ), and Collection.isEmpty( ).

  • Operations on NULL values do not throw a NullPointerException. Instead, they evaluate to false.

  • You use variable declarations to navigate through collections within the filter.

  • You cannot make assignments in your filters.

  • & is a logical "and"; && is a conditional "and".

    [ Team LiB ] Previous Section Next Section