9.2 Creating and Initializing a Query

The PersistenceManager interface contains a set of Query factory methods used to construct Query instances. They mainly differ in which query components are initialized. Query instances may be constructed at any time before a PersistenceManager is closed.

The following PersistenceManager method constructs an empty Query instance with none of the components initialized:

Query newQuery(  );

The following PersistenceManager methods construct a Query instance with an Extent as the collection of candidate instances:

Query newQuery(Extent candidates);
Query newQuery(Extent candidates, String filter);

The candidate class is initialized with the class of the Extent. The second method also initializes the query filter. We used this second method when we constructed the Query on line [3] in our example.

Alternatively, a collection can serve as the set of candidate instances in a query. The following PersistenceManager methods construct a Query instance with a Collection as the set of candidate instances:

Query newQuery(Class candidateClass, Collection candidates);
Query newQuery(Class candidateClass, Collection candidates, String filter);

When performing a query on a collection, it is necessary to specify the class of the candidate instances explicitly.

The elements in the collection should be persistent instances associated with the same PersistenceManager as the Query instance. If the collection contains instances associated with another PersistenceManager, a JDOUserException is thrown during execute( ). An implementation might allow you to perform a query on a collection of transient instances, but this is a nonportable, implementation-specific capability.

You can also construct a Query instance without initializing the set of candidate instances by calling one of the following PersistenceManager methods:

Query newQuery(Class candidateClass);
Query newQuery(Class candidateClass, String filter);

Once the Query is constructed, the collection of candidate instances can be set by calling one of its two setCandidates( ) methods, or it will default to the extent of the candidate class (including subclasses) identified by the candidateClass parameter passed to one of these two newQuery( ) methods. This allows you to perform a query without having to deal with an Extent.

A Query instance can be serialized. This allows you to create queries, serialize them, store them on disk, and later use them in a different execution environment. The serialized fields include the candidate class, the filter, parameter declarations, variable declarations, imports, the IgnoreCache setting, and the ordering specification. Of course, the candidate collection is not serialized with the Query instance. When a serialized Query instance is restored, it is no longer associated with its former PersistenceManager.

The following PersistenceManager method is used to construct a new Query instance from an existing or deserialized Query instance:

Query newQuery(Object query);

The query parameter might be a restored Query instance that was serialized from the same JDO implementation but a different execution environment, or it might be currently bound to a PersistenceManager from the same implementation. All of the query components from the query parameter are copied to the new Query instance, except for the candidate Collection or Extent. You can initialize this query component with a call to setCandidates( ).

Lastly, you can use the following PersistenceManager method to construct a Query that uses a query language different than JDOQL:

Query newQuery(String language, Object query);

The Query instance is constructed using the specified language and query parameters. The language parameter specifies the query language used by the query parameter. The query instance must be an instance of a class defined by the query language. For JDOQL, the value of the language parameter is "javax.jdo.query.JDOQL". The JDO specification does not specify other query languages that can be specified and used by this method; it is implementation-specific.

Once you have constructed a Query, you can access the PersistenceManager instance you originally used to create the Query instance by calling the following Query method:

PersistenceManager getPersistenceManager(  );

A null is returned if the Query was restored from a serialized form.

You can have multiple Query instances active simultaneously in the same PersistenceManager instance. The queries may be executed simultaneously by different threads, but the implementation may execute them serially. In either case, the execution is thread-safe.

The Query interface provides methods to bind query components before the query is executed. Their parameters replace the previously set query component (i.e., the methods are not additive). For example, if a query needs multiple variables, they all must be specified in the same call to declareVariables( ).

You can use the following Query methods to set the required components of the query, including the candidate class, candidate set, and filter:

void setClass(Class candidateClass);
void setCandidates(Collection candidates);
void setCandidates(Extent candidates);
void setFilter(String filter);

If you specify an Extent as the set of candidate instances, the candidate class defaults to the class of the Extent. When you perform a query on a collection, you need to specify the class of the candidate instances explicitly. In other words, if you pass a Collection to setCandidates( ), you must also call setClass( ) before compiling or executing the query.

If you specify the class of candidate instances but do not provide the collection of candidate instances, the collection defaults to the Extent of the candidate class, with subclass instances included. Therefore, each of the following approaches produces an equivalent Query initialization:

// Approach 1
Query query = pm.newQuery(MediaContent.class);

// Approach 2
Query query = pm.newQuery(  );

// Approach 3
Query query = pm.newQuery(pm.getExtent(MediaContent.class, true));

// Approach 4
Query query = pm.newQuery(  );
query.setCandidates(pm.getExtent(MediaContent.class, true));

If a collection serving as the set of candidates has an element that has been deleted by a call to deletePersistent( ), the element is ignored. If instances are added or removed from the candidates collection after setCandidates( ) is called, it is implementation-specific whether those elements take part in the query or a NoSuchElementException is thrown during execution of the query. So, you should not alter the collection once it has been passed to setCandidates( ).

You declare query parameters, variables, and their types after the Query has been constructed by calling the following methods:

void declareParameters(String parameters);
void declareVariables(String variables);
void declareImports(String imports);

The following method initializes the ordering specification:

void setOrdering(String ordering);

We cover each of these methods and their parameter syntax later in this chapter.