9.1 Query Components

The JDO query facility applies a Boolean filter to a collection of candidate instances and returns the instances that evaluate to true. The collection of candidate instances can be either an Extent or a Collection. The class of candidate instances is another query component. Instances are returned in the query result only if they are instances of the candidate class.

Let's begin by examining a method that performs a query that accesses Customer instances in the Media Mania model. We assume that an application has started a transaction and called queryCustomers( ), passing the PersistenceManager instance and values to filter the Customer instances to those whose addresses are in a specific city and state.

public static void queryCustomers(PersistenceManager pm,
                                  String city, String state) {
    Extent customerExtent = pm.getExtent(Customer.class, true);     [1]
    String filter = "address.city == city && state == address.state";     [2]
    Query query = pm.newQuery(customerExtent, filter);     [3]
    query.declareParameters("String city, String state");     [4]
    query.setOrdering(     [5]
        "address.zipcode ascending, lastName ascending, firstName ascending");
    Collection result = (Collection) query.execute(city, state);     [6]
    Iterator iter = result.iterator(  );
    while (iter.hasNext(  )) {     [7]
        Customer customer = (Customer) iter.next(  );
        Address address = customer.getAddress(  );
        System.out.print(address.getZipcode(  ));         System.out.print(" ");
        System.out.print(customer.getFirstName(  ));      System.out.print(" ");
        System.out.print(customer.getLastName(  ));       System.out.print(" ");
        System.out.println(address.getStreet(  ));
    }
    query.close(result);     [8]
}

This code performs a query on the Customer extent, which we access on line [1]. When we create the Query instance on line [3], we provide the Customer extent as the collection of candidate instances to be evaluated in the query. When you use an Extent, as we have here, it also identifies the class of the candidate instances. We use the candidate class to establish the namespace for the identifiers used in the query filter. Line [2] specifies the filter for the query. It uses the Customer field address and navigates to the associated Address instance to access the city and state fields. The city and state identifiers in the filter are query parameters, which are declared on line [4]. We access all Customer instances that live in a specific city and state. The Java == operator expresses equality, and the Java operator && performs a conditional AND operation. You will find JDOQL very easy to learn, because it uses Java operators and syntax. You also express your queries using the identifiers in your object model. On line [5], we establish an ordering for the instances that are in the query result. First we order customers based on their ZIP code; we then order all customers in the same ZIP code by their last name and then first name, all in ascending order. This ordering specification is similar to SQL's ORDER BY clause.

Line [6] executes the query. We pass the city and state method parameters to execute( ) as query parameters, which are also named city and state. It is not necessary for the method parameters to have the same names as the query parameters, but we do so to make it clear to anyone reading the code that they are associated. Line [4] declares the query parameters and their order. The order in this declaration establishes the order that the query parameter values should be passed to execute( ) on line [6].

The result of the query must be cast to a Collection in JDO 1.0.1. The execute( ) method is defined to return Object, to allow for future extensions that may return a single instance. In general, you should call iterator( ) only on the return value of execute( ). Once we have an Iterator, we can iterate through all the returned Customer instances. The code also navigates from the returned Customer instance to its associated Address instance. Once we are done with the query result, we close it on line [8].

Every query requires three components:

Class of candidate instances

This specifies the class of the instances that should be included in the query result. All of the candidate instances should be of this class or one of its subclasses. The class provides a scope for the names in the query filter, similar to the scope established for field names in a Java class definition. In the previous example, the Customer extent established the class of candidate instances when we called newQuery( ).

Collection of candidate instances

The collection of candidate instances is either a java.util.Collection or an Extent. We used the Extent for the Customer class in the previous example. We use the Extent when we intend the query to be filtered by the datastore, not by in-memory processing. The Collection might be a previous query result, allowing for subqueries. If you do not explicitly provide the collection of candidate instances but you do provide the class of candidate instances, the candidate collection defaults to the extent of the class of candidate instances, including subclass instances.

Any instances in the collection of candidate instances that are not of this class are silently ignored and are not included in the query result. This can occur when the set of candidate instances is a Collection containing instances of multiple classes.

Query filter

The query filter is a String that contains a Boolean expression that is evaluated for each instance in the candidate collection. The query result returns the candidate instances that have a true result for the query filter. If the query filter is not specified, the filter results in a true value for all of the candidate instances. The query filter in the previous example is specified on line [2].

The collection and class of the candidate instances and the query filter can be initialized when a Query is constructed by calling one of several newQuery( ) methods defined in the PersistenceManager interface (as we did on line [3]). Once a Query has been constructed, all of the query components can be set; each has an associated set method.

A query may also include the following components:

Parameters

A parameter provides a means of passing a value to be used in the query filter expression. Parameters serve a role similar to formal method parameters in Java. The query in our example had query parameters named city and state, declared on line [4]. The declaration of query parameters' name and type has the same syntax as method parameters. You provide a value for the query parameters when the query is executed.

Variables

A variable is used in a query filter to reference the elements of a collection. The use and declaration syntax of query variables is similar to the local variables in a method. Our example did not access elements of a collection, so we did not use a query variable. A variable is bound to the elements of a collection by a contains( ) expression (covered later in this chapter). Some implementations allow a variable that is not bound to a collection to be associated with an Extent. In this case, the variable is referred to as an unbound variable, and it may represent any instance in the extent of the class in the datastore.

Import statements

Parameters and variables can be of a class different from the candidate class; an import statement declares their type names. Types supported by JDO and defined in the java.lang package do not need to be imported. This includes the String class, the type of the query parameters in our example, so we did not need to import any types. Examples of import are provided later in this chapter.

Ordering specification

You can specify the order of the instances returned in the query result by providing an ordering specification, which is a list of expressions with an indicator to specify whether the values should be in ascending or descending order. We provided an ordering specification on line [5] in our example.

You need to create and initialize these query components before you execute a query. Query components can be initialized when a Query is constructed or via a set method provided for the query component. The order in which you initialize the query components before the Query is executed does not matter.