Named queries provide a means to define complex or commonly used queries in metadata files. These queries have all the capabilities of queries created in code, including support for parameters, aggregates and projections.
You declare named queries in .jdoquery files. The query file format is quite similar to that of JDO metadata. In addition, query metadata files are stored in the same locations as JDO metadata. The .jdoquery XML structure includes attributes and elements which correspond to methods in the javax.jdo.Query interface.
public Query newNamedQuery (Class cls, String name);
At runtime, you obtain named queries with the PersistenceManager.newNamedQuery method. The Class argument names the query's candidate class. This argument may be null if the query does not have a candidate class, as we'll see below. The String argument is the name of the query.
Until official JDO 2 jars are released, you will have to cast your persistence managers to the kodo.runtime.KodoPersistenceManager interface to access the newNamedQuery method.
Before returning a named query, the system populates it with the information provided in the .jdoquery metadata, then compiles it to make sure it is valid. You can still change the query before executing it, using the methods of the Query interface. These changes won't affect other query instances returned from subsequent calls to newNamedQuery.
The Document Type Definition (DTD) for named query metadata is very similar to JDO metadata. We discuss the various elements of the DTD below.
<!ELEMENT jdoquery ((query)*, (package)*, (extension)*)> <!ELEMENT package ((class)+)> <!ATTLIST package name CDATA #REQUIRED> <!ELEMENT query ((declare)?, (filter|sql|jdoql)?, (result)?, (extension)*)> <!ATTLIST query name CDATA #REQUIRED> <!ATTLIST query language CDATA #IMPLIED> <!ATTLIST query ignore-cache (true|false) #IMPLIED> <!ATTLIST query include-subclasses (true|false) #IMPLIED> <!ATTLIST query filter CDATA #IMPLIED> <!ATTLIST query sql CDATA #IMPLIED> <!ATTLIST query jdoql CDATA #IMPLIED> <!ATTLIST query ordering CDATA #IMPLIED> <!ATTLIST query range CDATA #IMPLIED> <!ELEMENT filter (#PCDATA)> <!ELEMENT sql (#PCDATA)> <!ELEMENT jdoql (#PCDATA)> <!ELEMENT declare (extension)*> <!ATTLIST declare imports CDATA #IMPLIED> <!ATTLIST declare parameters CDATA #IMPLIED> <!ATTLIST declare variables CDATA #IMPLIED> <!ELEMENT result (#PCDATA)> <!ATTLIST result unique (true|false) #IMPLIED> <!ATTLIST result class CDATA #IMPLIED> <!ATTLIST result grouping CDATA #IMPLIED> <!ELEMENT extension (extension)*> <!ATTLIST extension vendor-name CDATA #REQUIRED> <!ATTLIST extension key CDATA #IMPLIED> <!ATTLIST extension value CDATA #IMPLIED>
You can define queries just below the root jdoquery element or at the class level. Queries within the jdoquery root do not have an automatic candidate class. These are usually single-string JDOQL queries with an inline candidate class declaration, or SQL projections, which we cover in the next chapter. Queries denoted in a class element are given that candidate class.
The query element details options like the query name, ordering, range, and whether to include subclasses in the results. You can include the filter string as an attribute at this level or as the text of a nested filter element.
Use the declare element to control variable, parameter, and import declarations.
The result element allows you to use aggregates, projections, custom result classes, unique results, and grouping.
Finally, you can forgo most of the elements and attributes above and express the query in single-string form. Use the jdoql attribute on the query element, or the nested jdoql element.
We will now examine some named query examples. We begin by looking at the metadata for a very simple query.
Example 11.24. Defining a Named Query
The XML below defines a query named sports for the org.mag.Magazine class. You might place this XML in the org/mag/package.jdoquery or org/mag/Magazine.jdoquery files.
<?xml version="1.0"?> <jdoquery> <package name="org.mag"> <class name="Magazine"> <query name="sports" filter="title.startsWith ('Sports')"/> </class> </package> </jdoquery>
Declaring the named query above is equivalent to the following Java code:
PersistenceManager pm = ...; Query query = pm.newQuery (Magazine.class); query.setFilter ("title.startsWith ('Sports')"); query.compile ();
Here is a more complex case:
Example 11.25. Ordering, Range, Variables, and Parameters
<xml version="1.0"?> <jdoquery> <package name="org.mag"> <class name="Magazine"> <query name="findBySub" ordering="title ascending" range="0,10"> <declare parameters="String sub" variables="Article art"/> <filter> articles.contains (art) && art.subtitles.contains (sub) </filter> </query> </class> </package> </jdoquery>
This query finds the first 10 magazines that have an article with a subtitle equal to sub, where sub is a string whose value you supply on execution. The results are ordered alphabetically on the magazine title. Note that we declare parameters and variables as attributes of the declare element. In addition, we define behavior such as ordering and result range as attributes of the query element.
Example 11.26. Single-String Named Query
Here is the same query as the previous example, using single-string form and implicit parameters and variables. Because the single-string form includes the candidate class, you can use it outside of a class element.
<?xml version="1.0"?> <jdoquery> <query name="findBySub"> <jdoql> select from org.mag.Magazine where articles.contains (art) && art.subtitles.contains (:sub) order by title ascending range 0, 10 </jdoql> </query> </jdoquery>
Example 11.27. Aggregates and Projections
This example uses the result element to create an aggregate result. The query below returns each publisher and the average price of its magazines, for all publishers that publish less than 10 magazines. The results are packed into instances of the PubPrice class.
<?xml version="1.0"?> <jdoquery> <package name="org.mag"> <class name="Magazine"> <query name="publishPrice"> <result grouping="publisher having count (this) < 10" class="PubPrice"> publisher, avg(price) as avgPrice </result> </query> </class> </package> </jdoquery>
To obtain a named query you pass the query's candidate class (or null for queries outside of a class element) and name to the PersistenceManager. Once you have the Query, you can immediately execute it, optionally passing in values for any parameters the query declares.
Example 11.28. Executing Named Queries
PersistenceManager pm = ...; Query query = pm.newNamedQuery (Magazine.class, "findBySub"); Collection mags = (Collection) query.execute ("JDO"); for (Iterator itr = mags.iterator (); itr.hasNext ();) processMagazine ((Magazine) itr.next ()); query.close (mags);