Using Logical Cursors with Oracle TopLink

How-To Document

 

July 2006

After reading this How-To document, you will be able to use Oracle TopLink with your application to iterate over a large set of objects without using a scrollable cursor.

Software Requirements

Introduction

What if your application has to iterate over a large set of Objects? To accomplish this using TopLink, just use a scrollable cursor on the database. The code is straight forward:

ReadAllQuery query = new ReadAllQuery(Model.class);
query.useScrollableCursor(100);
ScrollableCursor cursor = (ScrollableCursor)session.executeQuery(query);

Unfortunately, most J2EE applications must avoid situations where the state of the cursor is managed by the JDBC connection. If they do not, the connection resource can not be shared while the ScrollableCursor remains open. Ultimately, the scalability of the application suffers. In order better share the database resource, it is a good idea to maintain the state of the cursor in the application, not in the JDBC Connection.

This document explores the following methods:

Using a Simple Logical Cursor

In order to efficiently share the JDBC connection resources, we will create "logical" cursors, in which the state of the cursor is stored in a simple Java object. This simple logical cursor is based on a ReportQuery.

You can perform an iteration over a set of objects using the following code:

LogicalCursor cursor = new LogicalCursor(Model.class,null);
  for (CursorIterator it = cursor.iterator(session); it.hasNext();)
  {
    System.out.println(it.next(session));
  }
 

Notice that the next(Session) method takes a TopLink session as an argument. During iteration, TopLink may need to go back to the database to fetch the next set of results. The state of the cursor, however, is maintained entirely by the LogicalCursor object. As the iteration progresses, it may need to use a database resource (through the TopLink Session), but it will immediately release it back to the shared pool of resources.

In essence, the functionality of any LogicalCursor is it's ability to store state allowing the cursor to remain open without tying up any expensive resources – such as (most importantly) database resources and memory resources.

One of the simplest solutions is to create a cursor which extracts the PrimaryKeys for each object in a large ResultSet. This cursor, along with it's set of PrimaryKeys values, can be stored in an HttpSession or a StatefulSessionBean. The sample code for a simple logical cursor such as this is included in the resources for this article (in LogicalCursor.java).

The most important features of LogicalCursor are

This implementation is very simple... and it does not tie up any database resource. However, this simple implementation can result in a database round trip every time the application code iterates to the next object (unless the object is currently in the TopLink cache). You can reduce database round trips by querying for a larger set of objects during each read operation: "Chunking" Logical Cursors.

"Chunking" Logical Cursors

Very similar to the "Using a Simple Logical Cursor" example, the code provided in ChunkingLogicalCursor.java takes two additional parameters in its constructor:

The idea behind the ChunkingLogicalCursor is to extract a set of objects using the SQL IN operator. The iterator returns objects until the current "chunk" is exhausted, at which point it goes to the database for the next "chunk".

Here is the fragment of code responsible for loading the next chunk of results. The entire code is included with the source for this How-To document (ChunkingLogicalCursor.java).

// create the next set of "IN" args
Vector args = new Vector();
for(Iterator resultsIterator = primaryKeyQueryResults
  .subList(this.current,this.current+chunkingSize).iterator();
  resultsIterator.hasNext();)
  {
    ReportQueryResult result = (ReportQueryResult)resultsIterator.next();
    args.add(result.get(field));
  }

// fetch the next chunk of Objects from the database
ReadAllQuery query = new ReadAllQuery(clazz);
query.setSelectionCriteria(query.getExpressionBuilder().get(field).in(args));

// update the state of the cursor
this.it = ((Vector)session.executeQuery(query)).iterator();
this.current += chunkingSize;

The ChunkingLogicalCursor reduces the number of round trips needed to the database. However, both of the cursors above need to store a set of sortable values over which to perform the iteration. In the next example ("Using a StatelessCursor") we will look at methods for reducing the amount of data required to store the current state of the logical cursor.

Using a StatelessCursor

A StatelessCursor can be reconstructed from very simple data (that is, a parameter embedded in a web page). For example, an application might need to re-constitute the state of a LogicalCursor using only the data from a set of HttpServletRequest parameters.

Imagine a scenario where a user iterates over a set of objects that contain a mapping to a sortable field (let's call this field "name"). Each time they want to perform an iteration, the application uses a "name" value to re-constitute the state of the cursor:

// re-initialize the state of the cursor from the "name" parameter
// chunking size is 50
StatelessCursor cursor = new StatelessCursor(Model.class,50,"name",name);

// iteration will begin at the Object with field name="name"
for (Iterator it = cursor.iterator(session); it.hasNext();)
{
  System.out.println(it.next());
}

This StatelessCursor relies on the "name" field mapping to a sortable field on the database so that we can use the greaterThan() operator to return the correct set of results:

ReadAllQuery query = new ReadAllQuery(this.clazz);
query.addOrdering(query.getExpressionBuilder().get(this.field).ascending());
query.setMaxRows(this.chunkSize);
query.setSelectionCriteria(query.getExpressionBuilder().get(this.field).greaterThan(this.start));
return ((Vector)session.executeQuery(query)).iterator();

The complete code for the StatelessCursor class is included in StatelessCursor.java.

Using the IndexedStatelessCursor

The final example we will discuss involves a generalization of the previous pattern where we do not necessarily require the sortable field. The key difference with IndexedStatelessCursor.java is that the state of the cursor is re-constituted with a single integer argument. This particular implementation does not depend on a sortable mapped field in the object. Notice how the field this.index is used in the code below:

ReadAllQuery query = new
ReadAllQuery(this.clazz);

query.addAscendingOrdering("id");
// get cursor and set it to the correct index

query.useScrollableCursor(this.chunkSize);

ScrollableCursor cursor = (ScrollableCursor)session.executeQuery(query);
if (this.index>0)
{
  cursor.absolute(this.index);
}

// extract Object results from the cursor
ArrayList results = new ArrayList(this.chunkSize);
for (int i=0; i<this.chunkSize; i++)
{
  results.add(cursor.next());
}

// close cursor!!!!!!!!!!!
cursor.close();

this.index += this.chunkSize;
return results.iterator();

We seem to have come full circle here as we are back to using a ScrollableCursor. However, there is an important difference: The ScrollableCursor is used only to position the absolute index. After it has extracted the correct set of objects, it is closed and abandoned. The database resource is used for as small amount of time as possible, and the only state that has to be saved by the application is the current index value.

The final approach has the disadvantage that the database must perform the entire query each time the cursor is re-created. Also, if new rows are inserted/deleted in between client requests, these changes will be reflected in the results. Depending on the particular application of the cursor, this may be good or bad. The first two queries hold much more state and can come closer to providing a view of the data from the time of the initial query.

Summary

We have presented some ideas for how the TopLink query API can be used to implement various LogicalCursor designs. None of these patterns will result in a single Thread holding on to a database resource throughout the lifetime of the "cursor". However, each pattern differs in how much information the cursor needs to store, and in what kinds of fields need to be present in the model.

Related Documents

For additional information, refer to the Oracle TopLink API Reference and following sections in the Oracle TopLink Developer's Guide:

Sample Files

The following table describes the sample files included with the source code download (dist.zip) used in this How-To document.

Java File State of Cursor Chunking? Requires Sorting?
LogicalCursor.java Vector of PrimaryKey values (may be composite Primary Keys) No No
ChunkedLogicalCursor.java Vector values from a sortable Column Yes Yes
StatelessCursor.java java.lang.Object from a sortable Column Yes Yes
IndexStatelessCursor.java int Yes No


Oracle TopLink 10g Release 3 (10.1.3.0)

The Programs (which include both the software and documentation) contain proprietary information; they are provided under a license agreement containing restrictions on use and disclosure and are also protected by copyright, patent, and other intellectual and industrial property laws. Reverse engineering, disassembly, or decompilation of the Programs, except to the extent required to obtain interoperability with other independently created software or as specified by law, is prohibited.

The information contained in this document is subject to change without notice. If you find any problems in the documentation, please report them to us in writing. This document is not warranted to be error-free. Except as may be expressly permitted in your license agreement for these Programs, no part of these Programs may be reproduced or transmitted in any form or by any means, electronic or mechanical, for any purpose.

If the Programs are delivered to the United States Government or anyone licensing or using the Programs on behalf of the United States Government, the following notice is applicable:

U.S. GOVERNMENT RIGHTS Programs, software, databases, and related documentation and technical data delivered to U.S. Government customers are "commercial computer software" or "commercial technical data" pursuant to the applicable Federal Acquisition Regulation and agency-specific supplemental regulations. As such, use, duplication, disclosure, modification, and adaptation of the Programs, including documentation and technical data, shall be subject to the licensing restrictions set forth in the applicable Oracle license agreement, and, to the extent applicable, the additional rights set forth in FAR 52.227-19, Commercial Computer Software--Restricted Rights (June 1987). Oracle USA, Inc., 500 Oracle Parkway, Redwood City, CA 94065

The Programs are not intended for use in any nuclear, aviation, mass transit, medical, or other inherently dangerous applications. It shall be the licensee's responsibility to take all appropriate fail-safe, backup, redundancy and other measures to ensure the safe use of such applications if the Programs are used for such purposes, and we disclaim liability for any damages caused by such use of the Programs.

Oracle, JD Edwards, PeopleSoft, and Siebel are registered trademarks of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

The Programs may provide links to Web sites and access to content, products, and services from third parties. Oracle is not responsible for the availability of, or any content provided on, third-party Web sites. You bear all risks associated with the use of such content. If you choose to purchase any products or services from a third party, the relationship is directly between you and the third party. Oracle is not responsible for: (a) the quality of third-party products or services; or (b) fulfilling any of the terms of the agreement with the third party, including delivery of products or services and warranty obligations related to purchased products or services. Oracle is not responsible for any loss or damage of any sort that you may incur from dealing with any third party.