In this section, we explore advanced topics in object filtering,
supported by the following Query
methods:
public void declareParameters (String parameters); public void declareVariables (String variables); public void declareImports (String imports);
The declareParameters
method defines
the names and types of the query's parameters. Declaring query
parameters is analogous to declaring the parameter signature of
a Java method, and uses the same syntax. Parameters acts as
placeholders in the filter string, allowing you to supply new
values on each execution. Parameters also allow for
query by example, as demonstrated in
Example 11.8, “Query By Example”.
Parameters do not have to be declared. As you will see in the examples below, you can introduce implicit parameters into your queries simply by prefixing the parameter name with a colon in your JDOQL string. Wherever the type of an implicit parameter can't be inferred from the context, you can set the type by casting the parameter in the JDOQL.
You cannot mix implicit and declared parameters in the same query. Each query must declare all of its parameters, or none of them.
declareVariables
names the local
variables used in the query filter. It uses standard Java
variable declaration syntax. Query variables are typically
used to place conditions on elements of collections or maps.
Like parameters, variables do not have to be declared. Whenever you use an unrecognized identifier where a variable would be appropriate, the JDO implementation will dynamically define an implicit variable with that name. In the case of an unconstrained variable, you must cast the variable in your JDOQL to supply its type. Unlike parameters, you can mix both declared and implicit variables in the same query.
You can import classes into the query's namespace with the
declareImports
method. The method
argument uses standard Java import
syntax.
By default, queries only recognize unqualified class names when
the class is in the package of the candidate class, or when it
is in java.lang
. Using imports, you can
save yourself the trouble of typing out full class names
in your parameter and variable declarations, and in your
your filter string (class names can appear in filter strings
when you use the instanceof
operator,
access a static field, or perform a cast).
The following examples will give you a feel for the query elements described above. They use the object model we defined in the previous section.
Example 11.6. Imports and Declared Parameters
Find the magazines with a certain publisher and title, where the publisher and title are supplied as parameters on each execution.
PersistenceManager pm = ...; Company comp = ...; String str = ...; Query query = pm.newQuery (Magazine.class, "publisher == pub && title == ttl"); query.declareImports ("import org.mag.pub.*"); query.declareParameters ("String ttl, Company pub"); List mags = (List) query.execute (str, comp); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ()); query.close (mags);
There are two things to take away from this example. First, the
import prevents us from having to qualify the Company
class name when declaring our pub
parameter (although in this case, it would have been easier to
just write out the full class name in the parameter declaration).
The unqualified Company
name is not
automatically recognized by the query because Company
isn't in the same package as
Magazine
, the candidate class.
Second, notice that we supply values for the parameter
placeholders when we execute the query. Parameters can be of
any type recognized by JDO, though primitive parameters have
to be supplied as instances of the appropriate wrapper type on
execution. The Query
interface includes
several methods for executing queries with various numbers of
parameters. When you use declared parameters, you should supply
the parameter values in the same order that you declare the
parameters.
Example 11.7. Implicit Parameters
The query below is exactly the same as our previous example, except this time we use implicit parameters.
PersistenceManager pm = ...; Company comp = ...; String str = ...; Query query = pm.newQuery (Magazine.class, "publisher == :pub && title == :ttl"); List mags = (List) query.execute (comp, str); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ()); query.close (mags);
Here, we use colon-prefixed names to introduce new parameters without declarations. When we execute the query, we supply the parameter values in the order the implicit parameters first appear in the JDOQL. Later in the chapter, we'll see queries that consist of multiple JDOQL strings. Each string might introduce new implicit parameters. When deciding the proper order to supply the parameter values, start with the parameters in the result string, then those in the filter, then the grouping string, and finally the ordering string.
In the query above, we can infer the type of each parameter based on its context. This is almost always the case. There are times, however, when the context alone is not enough to determine the type of an implicit parameter. In these cases, use a cast to supply the parameter type:
PersistenceManager pm = ...; Company comp = ...; Query query = pm.newQuery (Magazine.class, "publisher.revenue == ((org.mag.pub.Company) :pub).revenue"); List mags = (List) query.execute (comp); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ()); query.close (mags);
Example 11.8. Query By Example
Parameters do not need to be in the datastore to be useful. You can implement query by example in JDO by using an existing "example" object as a query parameter:
PersistenceManager pm = ...; Magazine example = new Magazine (); example.setPrice (100); example.setTitle ("Fourier Transforms"); Query query = pm.newQuery (Magazine.class, "price == ex.price && title == ex.title"); query.declareParameters ("Magazine ex"); List mags = (List) query.execute (example); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ());
Example 11.9. Variables
Find all magazines that have an article titled "Fourier Transforms".
PersistenceManager pm = ...; Query query = pm.newQuery (Magazine.class, "articles.contains (art) " + "&& art.title == 'Fourier Transforms'"; query.declareVariables ("Article art"); List mags = (List) query.execute (); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ()); query.close (mags);
A variable represents any persistent instance of its declared type.
So you can read the filter string above as: "The magazine's
articles
collection contains some article
art
, where art
's title is
'Fourier Transforms'". Notice how we bind art
to a particular collection with the contains
method, then test its properties in an &&'d
expression. This is a common pattern in JDOQL filters, and applies
equally well to placing conditions on the keys and values of maps.
Of course, we don't have to declare art
explicitly. The same query without declareVariables
would work just as well:
PersistenceManager pm = ...; Query query = pm.newQuery (Magazine.class, "articles.contains (art) " + "&& art.title == 'Fourier Transforms'"; List mags = (List) query.execute (); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ()); query.close (mags);
In this case the JDO implementation assumes art
is an implicit variable because it does not match the name of any
field in the candidate class. The new variable's type is set to
the known element type of the collection that contains it.
Example 11.10. Unconstrained Variables
The example above uses a variable to represent any element in a collection. We refer to variables used to test collection or map elements as constrained or bound variables, because the values of the variable are limited by the collection or map involved. Many JDO implementations also support unconstrained variables. Rather than representing a collection or map element, an unconstrained variable represents any persistent instance of its class. Consider the following example:
Query query = pm.newQuery (Article.class, "mag.copiesSold > 10000 " + "&& mag.coverArticle == this"); query.declareVariables ("Magazine mag"); List arts = (List) query.execute (); for (Iterator itr = arts.iterator (); itr.hasNext ();) processArticle ((Article) itr.next ()); query.close (arts);
What does this query do? Let's break it down. The first clause
matches any magazine that has sold more than 10,000 copies. The
second clause requires that the cover article of the magazine is the
candidate instance being evaluated (notice the query's candidate
class is Article
in this example). So the
query returns all articles that are the cover article for a
magazine that has sold more than 10,000 copies. The unconstrained
variable mag
allowed us to overcome the fact that
there was no direct relation from Article
to
Magazine
(only the reverse).
Later in this chapter, we'll see how to use a projection to drastically simplify this query.
We can also execute this query without declaring the mag
variable explicitly. Without a constraining
contains
clause, however, the type of an
unconstrained, implicit variable is impossible to infer. Use a cast
to supply the type:
Query query = pm.newQuery (Article.class, "((Magazine) mag).copiesSold > 10000 " + "&& mag.coverArticle == this"); List arts = (List) query.execute (); for (Iterator itr = arts.iterator (); itr.hasNext ();) processArticle ((Article) itr.next ()); query.close (arts);
Up until now, we have focused on how to configure our queries with the right filter, but we have ignored how to actually execute the query. The next section corrects this oversight.