ObjectStore Java Tutorial
Chapter 5

Using ObjectStore to Query a Database

ObjectStore provides a mechanism for querying COM.odi.util.Collection objects. A query applies a predicate expression (an expression that evaluates to a boolean result) to all elements in a collection. The query returns a subset collection that contains all elements for which the expression is true.

To accelerate the processing of queries on particularly large collections, you can build indexes on the collection.

This chapter discusses the following topics:

Querying Collections

Using Indexes to Speed Query Performance

Querying Collections

Using queries involves a two-step process:

  1. Create the query.

  2. Run the query against a collection.

Creating Queries

To create a query, you run the Query constructor and pass in a Class object and a query string. The Class object specifies the type of element contained by the collection you want to query. This element type must be a publicly accessible class, and any members (fields or methods) specified in the query string must be publicly accessible as well.

The query string is a predicate expression, which is defined with native Java. There is no SQL (Structured Query Language) or JDBC required. Query strings can include standard Java arithmetic, conditional, and relational operators, as well as simple methods. The following example shows creation of a query that finds all interests where the interest name is "wine":

Query query = new Query(Interest.class, "getName() == \"wine\"");
This example uses a public method instead of a field. This allows the Interest name field to remain private and preserves the encapsulation of the interest's state. If the name field was public, you could specify the query like this:

Query query = new Query(Interest.class, "name == \"wine\"");
When you create a query, you do not bind it to a particular collection. You can create a query, run it once, and throw it away. Alternatively, you can reuse a query multiple times against the same collection, or against different collections.

You can also use variables in query strings. See Specifying Variables in Queries.

Running Queries Against Collections

You run a query against a specific collection with a call to the Query.select() method. The call specifies the collection to be queried. For example, after you define a query as in the previous section, you can run that query like this:

Collection wine = query.select(allInterests);
In this example, the query tests the elements in the set of allInterests to find the elements whose name is wine.

Specifying Variables in Queries

You can use variables in queries instead of constants. For example, in the previous example you might want to substitute for different name values, depending on whether you are looking for wine, food, or some other interest. The following example shows how to use a variable value in a query expression:

String interestName = "wine";
FreeVariables freeV = new FreeVariables();
freeV.put("x", String.class);
Query query = new Query(Interest.class, "getName() == x", freeV);
FreeVariableBindings freeVB = new FreeVariableBindings();
freeVB.put("x", interestName);
Collection queryResults = query.select(
      allInterests.values(), freeVB);
First, create a FreeVariables list and add a variable, x in this example, to it. When you add the variable, you specify the type of the variable. In this case, the type of x is String because it is going to represent a String in the query string. When you create the query,

Binding variables

Before you can execute the query, you must bind the variable in the query string to a variable in the program. To do this, create a FreeVariableBindings list. In this example, freeVB binds the variable x to the variable interestName.

Sample query with a variable

When you execute the query, pass the FreeVariableBindings list as an argument to the query select() method. For example, here is a method that finds all users with a particular interest, which is specified as an argument to the method.

/**
 * Get all users with a particular interest.
 *
 * @param interestName: The name of the interest.
 *
 * @return An array of names of users with this interest.
 **/
public static String[] find Users(String interestName)
      throws PersonalizationException
{
      Transaction tr = Transaction.begin(ObjectStore.READONLY);
      String queryString = "getName() == \"" + interestName + "\"";
      Query interestQuery = new Query(Interest.class, queryString);
      Collection interests = interestQuery.select(allInterests);
      String[] users = new String[interests.size()];
      int index = 0;
      Iterator iter = interests.iterator();
      while (iter.hasNext())
            users[index++] = ((Interest)iter.next()).getUser().getName();
      tr.commit(ObjectStore.RETAIN_READONLY);
      return users;
}

Using Indexes to Speed Query Performance

When you want to run a query on a particularly large collection, it is useful to build indexes on the collection to accelerate query processing. You can add indexes to any collection that implements the COM.odi.util.IndexedCollection interface. This interface provides methods for adding and removing indexes, and updating indexes when the indexed data changes.

While querying is supported on all COM.odi.util collections, only COM.odi.util.OSTreeSet already implements the IndexedCollection interface. This means that if you want to add an index to another type (other than OSTreeSet) of collection, you must define a collection class that implements the IndexedCollection interface.

What an Index Does

An index provides a reverse mapping from a field value or from the value returned by a method when it is called, to all elements that have the value. A query that refers to an indexed field executes faster. This is because it is not necessary to examine each object in the collection to determine which elements match the predicate expression. Also, ObjectStore does not need to fetch into memory the elements that do not match the query.

To use an index, you create it and then specifiy the indexed field or method in a query. A query can include both indexed fields/methods and nonindexed fields/methods. ObjectStore evaluates the indexed fields and methods first and establishes a preliminary result set. ObjectStore then applies the nonindexed fields/methods to the elements in the preliminary result set.

Creating an Index

Use these methods, defined on IndexedCollection, to create an index:

addIndex(Class, String) 
addIndex(Class, String, boolean, boolean) 
The Class argument indicates the element type to which the index applies. The String indicates the element member to be indexed.

The optional boolean arguments allow you to specify whether the index is ordered and whether it allows duplicates. If you do not specify the boolean arguments, the index is unordered and it allows duplicates.

Example of Creating an Index

The following example shows the creation of an index on the return value from the getName() method on the Interest class. To execute this, the collection that contains the Interest objects, allInterests, must implement the IndexedCollection interface. In this example, this is accomplished by specifying allInterests to be an OSTreeSet collection. For example, you could have the following code in the UserManager.initialize() method in the Personalization application:

db.createRoot("allInterests", allInterests = new OSTreeSet(db));
allInterests.addIndex(Interest.class, "getName()");

Maintenance Required After Changing Indexed Elements

After you add an index to a collection, ObjectStore automatically maintains the index as you add or remove elements from the collection. However, it is your responsibility to update the index when indexed members change in instances that are already elements of an indexed collection.

For example, suppose you insert Jones into a collection called userCollection and then you build an index for userCollection on the email field. If you remove Jones from the collection, ObjectStore updates the email index so it no longer includes the entry for the Jones object. However, if you leave Jones in the collection, but change Jones' email address, you must manually update the index to contain the correct email entry.

To update an index, you must

  1. Remove the incorrect instance from the index. For example, remove Jones from the index.

  2. Update the incorrect instance. For example, modify the email address for Jones.

  3. Add the updated instance to the index. For example, add the updated Jones object to the index.

An example follows. For more information, see ObjectStore Java API User Guide, Enhancing Query Performance with Indexes.

User jones = /* assume jones references Jones */
userCollection.removeFromIndex(User.class, "email", jones);
jones.setEmail("jones@objectdesign.com");
userCollection.addToIndex(User.class, "email", jones);



[previous] [next]

Copyright © 1998 Object Design, Inc. All rights reserved.

Updated: 10/07/98 07:05:56