JDO : JDOQL Declarative API

As shown earlier, there are two primary ways of defining a JDOQL query. In this guide we describe the declarative approach, defining the individual components of the query via an API. You can also refer to the API javadoc , We firstly need to look at a typical declarative JDOQL 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.

setClass()

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.


setUnique()

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.

Sometimes you know that the query can only every return 0 or 1 objects. In this case you can simplify your job by adding

query.setUnique(true);

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. Note that if you specify unique and there are more results than just 1 then it will throw a JDOUserException.


setResult()

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.

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.


setResultClass()

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[].

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.


setRange()

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.

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

query.setRange(10, 20);

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.

setFilter()

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.


declareImports()

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.

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");

declareParameters()

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.

When using explicit parameters you need to declare them and their types. With the declarative API you do it like this

query.declareImports("import java.util.*");
query.declareParameters("String myparam1, Date myparam2");

So we make use of imports to define some package names (just like in Java). You can use * notation too. Note that java.lang is not needed to be imported. Alternatively you could have just done

query.declareParameters("java.lang.String myparam1, java.util.Date myparam2");

declareVariables()

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.

With explicit variables, you declare your variables and their types. In declarative JDOQL it is like this

query.declareVariables("mydomain.Product prod");

Multiple variables can be declared using semi-colon (;) to separate variable declarations.

query.declareVariables("String var1; String var2");

setOrdering()

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.

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");

setGrouping()

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.