As shown earlier, there are two ways of defining the content of a query;
using a single-string form, and using the API. Let's try to understand the Query API in JDO
,
We firstly need to look at a typical Query.
Query query = pm.newQuery(MyClass.class);
query.setFilter("field2 < threshold");
query.declareImports("import java.util.Date");
query.declareParameters("Date threshold");
query.setOrdering("field1 ascending");
List results = (List)query.execute(my_threshold);
In this Query, we create it to return objects of type
mydomain.MyClass
(or subclasses),
and set the filter to restrict to instances of that type which have the field
field2
less than some threshold value, which we don't know at that point.
We've specified the query like this because we want to pass the threshold value in dynamically.
We then import the type of our
threshold
parameter, and the parameter itself, and set the
ordering
of the results from the Query to be in ascending order of some field
field1
.
The Query is then executed, passing in the threshold value. The example is to highlight the typical
methods specified for a Query. Clearly you may only specify the Query line if you wanted something
very simple. The result of the Query is cast to a List since in this case it returns a List of
results.
Let's review the methods of the JDO Query API. Not all methods apply to all query languages.
The differences are noted in the sections below
Set the class of the candidate instances of the query. The class specifies the class of the
candidates of the query. Elements of the candidate collection that are of the specified class
are filtered before being put into the results.
This is applicable to JDOQL, SQL,
JPQL.
Specify that only the first result of the query should be returned, rather than a collection.
The execute method will return null if the query result size is 0.
This is applicable to JDOQL, SQL,
JPQL.
Sometimes you know that the query can only every return 0 or 1 objects. In this case you can
simplify your job by adding
In this case the return from the execution of the Query will be a single Object, so you've no
need to use iterators, just cast it to your candidate class type.
Specifies what type of data this query should return. If this is unset or set to null, this
query returns instances of the query's candidate class. If set, this query will return
expressions, including field values (projections) and aggregate function results.
This is applicable to JDOQL.
The normal behaviour of JDOQL queries is to return a List of Objects of the type of the candidate
class. Sometimes you want to have the query perform some processing and return things like count(),
min(), max() etc. You specify this with
query.setResult("count(param1), max(param2), param3");
In this case the results will be List<Object[]> since there are more than 1 column in each
row. If you have only 1 column in the results then the results would be List<Object>.
If you have only aggregates (sum, avg, min, max, count) in the result clause then there will be
only 1 row in the results and so the results will be of the form Object[] (or Object if only 1
aggregate).
Please refer to JDOQL Result Clauses for more details.
Specify the type of object in which to return each element of the result of invoking execute().
If the result is not set or set to null, the result class defaults to the candidate class of
the query. If the result consists of one expression, the result class defaults to the type of
that expression. If the result consists of more than one expression, the result class defaults
to Object[].
This is applicable to JDOQL, SQL.
When you perform a query, using JDOQL or SQL the query will, in general, return a List of objects.
These objects are by default of the same type as the candidate class. This is good for the majority
of situations but there are some situations where you would like to control the output object.
This can be achieved by specifying the
Result Class
.
query.setResultClass(myResultClass);
The
Result Class
has to meet certain requirements. These are
-
Can be one of Integer, Long, Short, Float, Double, Character, Byte, Boolean, String,
java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.sql.Date, java.sql.Time,
java.sql.Timestamp, or Object[]
-
Can be a user defined class, that has either a constructor taking arguments of the same type
as those returned by the query (in the same order), or has a public put(Object, Object) method,
or public setXXX() methods, or public fields.
Where you have a query returning a single field, you could specify the
Result Class
to be
one of the first group for example. Where your query returns multiple fields then you can set the
Result Class
to be your own class. So we could have a query like this
Query query = pm.newQuery(pm.getExtent(Payment.class,false));
query.setFilter("amount > 10.0");
query.setResultClass(Price.class);
query.setResult("amount, currency");
List results = (List)query.execute();
and we define our
Result Class
Price
as follows
public class Price
{
protected double amount = 0.0;
protected String currency = null;
public Price(double amount, String currency)
{
this.amount = amount;
this.currency = currency;
}
...
}
In this case our query is returning 2 fields (a Double and a String), and these map onto the constructor
arguments, so DataNucleus will create objects of the
Price
class using that constructor. We could
have provided a class with public fields instead, or provided
setXXX
methods or a
put
method. They all work in the same way.
Set the range of results to return. The execution of the query is modified to return only a
subset of results. If the filter would normally return 100 instances, and fromIncl is set
to 50, and toExcl is set to 70, then the first 50 results that would have been returned are
skipped, the next 20 results are returned and the remaining 30 results are ignored.
An implementation should execute the query such that the range algorithm is done at the data
store.
This is applicable to JDOQL.
Sometimes you have a Query that returns a large number of objects. You may want to just display
a range of these to your user. In this case you can do
This has the effect of only returning items 10 through to 19 (inclusive) of the query's results.
The clear use of this is where you have a web system and you're displaying paginated data, and
so the user hits page down, so you get the next "n" results.
setRange
is implemented efficiently for MySQL, Postgresql, HSQL (using the LIMIT SQL
keyword) and Oracle (using the ROWNUM keyword), with the query only finding the objects required
by the user directly in the datastore. For other RDBMS the query will retrieve all objects up
to the "to" record, and will not pass any unnecessary objects that are before the "from" record.
Set the filter for the query. The filter specification is a String containing a Boolean
expression that is to be evaluated for each of the instances in the candidate collection.
If the filter is not specified, then it defaults to "true", which has the effect of filtering
the input
Collection
only for class type.
This is applicable to JDOQL.
Set the import statements to be used to identify the fully qualified name of variables or
parameters. Parameters and unbound variables might come from a different class from the
candidate class, and the names need to be declared in an import statement to eliminate
ambiguity. Import statements are specified as a String with semicolon-separated statements.
This is applicable to JDOQL.
In JDOQL you can declare parameters and variables. Just like in Java it is often convenient to just
declare a variable as say Date, and then have an import in your Java file importing the
java.util.Date class. The same applies in JDOQL. Where you have defined parameters or variables
in shorthand form, you can specify their imports like this
query.declareVariables("Date startDate");
query.declareParameters("Locale myLocale");
query.declareImports("import java.util.Locale; import java.util.Date;");
Just like in Java, if you declare your parameters or variables in fully-specified form (for
example "java.util.Date myDate") then you do not need any import.
The JDOQL uses the
imports
declaration to create a
type namespace
for the query.
During query compilation, the classes used in the query, if not fully qualified, are searched
in this namespace. The type namespace is built with the following:
-
primitives types
-
java.lang.* package
-
package of the candidate class
-
import declarations (if any)
To resolve a class, the JDOQL compiler will use the class fully qualified name to load it, but
if the class is not fully qualified, it will search by prefixing the class name with the
imported package names declared in the type namespace.
All classes loaded by the query must be acessible by either the candidate class classloader,
the PersistenceManager classloader or the current Thread classloader. The search algorithm for
a class in the JDOQL compiler is the following:
-
if the class is fully qualified, load the class.
-
if the class is not fully qualified, iterate each package in the type namespace and try to
load the class from that package. This is done until the class is loaded, or the type
namespace package names are exausted. If the class cannot be loaded an exception is thrown.
Note that the search algorithm can be problematic in performance terms if the class is not
fully qualified or declared in imports using package notation. To avoid such problems, either
use fully qualified class names or import the class in the imports declaration. The 2 queries
below are examples of good usage:
query.declareImports("import java.util.Locale;");
query.declareParameters("Locale myLocale");
or
query.declareParameters("java.util.Locale myLocale");
However, the below example will suffer in performance, due to the search algorithm.
query.declareImports("import java.math.*; import java.util.*;");
query.declareParameters("Locale myLocale");
Declare the list of parameters query execution. The parameter declaration is a String containing
one or more query parameter declarations separated with commas. Each parameter named in the
parameter declaration must be bound to a value when the query is executed.
This is applicable to JDOQL.
When specifying JDOQL queries you can use normal Java classes in the query. You need to define to
JDOQL which class you are talking about. You can do this via the following calls
query.declareImports("import mypackage.myclass; import mypackage2.*");
query.declareParameters("String myparam1, Date myparam2");
This tells JDO (and more specifically DataNucleus) to look for any classes specified as parameters,
in the defined list of imports. java.lang is imported automatically here so you don't need to
specify this. It should be noted that if you specify a package in the imports (using * notation),
then DataNucleus will have to search for your class in that package and this can have a (small)
performance impact. You can get around this by not using "*" notation on imports. What are shown
above are called
explicit parameters
.
In JDO2 you can alternatively utilise
implicit parameters
. This is done by specifying the parameter
name in the query directly (prefixed by a colon). So you may write a Single-String query like
Query query = pm.newQuery("SELECT FROM mydomain.Product WHERE price < :limit");
List results = (List)query.execute(new Double(200.0));
Here we haven't declared the parameter. We simply prefixed it with a colon, and its type will be
determined implicitly from the query.
In some situations you may have a map of parameters keyed by their name, yet the query
in question doesn't need all parameters. Normal JDO execution would throw an exception
here since they are inconsistent with the query. You can omit this check by setting
q.addExtension("datanucleus.query.ignoreParameterCountCheck", "true");
Declare the unbound variables to be used in the query. Variables might be used in the filter,
and these variables must be declared with their type. The unbound variable declaration is a
String containing one or more unbound variable declarations separated with semicolons.
This is applicable to JDOQL.
In JDOQL you can connect two parts of a query using something known as a variable. For example,
we want to retrieve all objects with a collection that contains a particular element, and where
the element has a particular field value. We define a query like this
Query query = pm.newQuery(
"SELECT FROM mydomain.Supplier WHERE products.contains(prod) && prod.name == \"Beans\"");
So we have a variable in our query called "prod" that connects the two parts. We now declare it
query.declareVariables("mydomain.Product prod");
This is known as an
explicit variable
, since it is declared explicitly. Multiple variables
can be declared using semi-colon (
;
) to separate variable declarations.
query.declareVariables("String var1; String var2");
JDO2 also defines
implicit variables
, where we skip the
declareVariables
call and rely
on the JDO implementation to work out the type of the variable.
Set the candidate Collection to query.
Used where you have some instances and want to know which pass a specified filter.
This will result in the query being performed in-memory rather than in the datastore.
[Note that in legacy JDOQL/JPQL implementations for RDBMS this will still go to the
datastore].
This is applicable to JDOQL.
Set the ordering specification for the result Collection. The ordering specification is a
String containing one or more ordering declarations separated by commas. Each ordering
declaration is the name of the field on which to order the results followed by one of the
following words: "ascending" or "descending". The field must be declared in the candidate
class or must be a navigation expression starting with a field in the candidate class.
This is applicable to JDOQL.
With JDOQL you can specify the ordering using the normal JDOQL syntax for a parameter, and then
add
ascending
or
descending
(UPPER or lower case are both valid) are to give the
direction. In addition the abbreviated forms of
asc
and
desc
(again, UPPER and
lower case forms are accepted) to save typing. For example, you may set the ordering as follows
query.setOrdering("productId DESC");
Set the grouping expressions, optionally including a "having" clause. When grouping is
specified, each result expression must either be an expression contained in the grouping,
or an aggregate evaluated once per group.
This is applicable to JDOQL.