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