Skip Headers

Oracle Application Server TopLink Application Developer's Guide
10g (9.0.4)

Part Number B10313-01
Go To Documentation Library
Home
Go To Product List
Solution Area
Go To Table Of Contents
Contents
Go To Index
Index

Go to previous page Go to next page

6
Queries

Queries are a key element to any Oracle Application Server TopLink application, because they enable OracleAS TopLink to manage persistent data on the database. The query framework that OracleAS TopLink provides gives you the flexibility you need to manage the complex persistence requirements of enterprise applications.

The OracleAS TopLink query framework offers the following key features:

To define OracleAS TopLink queries, use the OracleAS TopLink Mapping Workbench, the OracleAS TopLink API, or in the case of entity beans, EJB Finders.

This chapter introduces OracleAS TopLink queries, and includes discussions on:

Introduction to Query Concepts

Queries are the cornerstone of OracleAS TopLink applications. Queries enable you to retrieve information or objects from the database, modify or delete those objects, and create new objects on the database.

The following concepts are key to understanding OracleAS TopLink queries:

Query Types

The type of query you build determines the type of result set the query returns. You can build:

Object Queries

Object queries, the most common query type in an OracleAS TopLink application, enable you to search a database for persistent objects. OracleAS TopLink offers two object query mechanisms: a readObject query that searches the database for a single object that matches the search criteria, and a readAll query that searches for all matching objects.

Object queries search for objects rather than data. For example, a query to find all employees over the age of 40 searches for objects--the employees.

Summary Queries

Summary queries enable you to search for partial information about objects that match your search criteria. There are two types of summary queries:

Data Queries

Data queries enable you to query data fields directly from the database tables rather than objects. Data queries represent a common approach to working with unmapped data, such as foreign keys and object version fields.

Object Write Queries

Object write queries enable you to modify data and objects directly on the database. You can use write queries to insert and update objects on the database. Write queries are useful when you manage simple, nonbusiness object data that have no relationships, such as user preferences.

For more information about write queries, see "Query Objects and Write Operations".

To avoid concurrency issues when you write more complex data to the database, use the Unit of Work.

For more information, see "Unit of Work Basics".

Query Components

Query components are the mechanisms with which you build your query. These components include:

OracleAS TopLink Expressions

The OracleAS TopLink expression framework is a querying syntax. Expressions enable you to specify search criteria in a query, based on the object model. They provide support for standard boolean operators, such as AND, OR, and NOT and support many database functions and operators.

You can create expressions in the OracleAS TopLink Mapping Workbench or in the OracleAS TopLink API.

For more information, see "Expressions".

Query by Example

Limited in complexity, query by example is an intuitive way to express a query. To specify a query by example, provide sample instances of the persistent objects to query, and specify the fields and values that define the query. You can use any valid constructor to create an example object.

For more information, see "Query by Example".

Stored Procedures

A stored procedure is a function, such as Procedural Language/Structured Query Language (PLSQL) statement or Java code, written on the database. Stored procedures enable you to execute logic and access data on the database server.

For more information, see "Stored Procedure Calls".

EJB QL

EJB QL presents queries from an object model perspective, enabling users to declare queries using the attributes of each abstract entity bean in the object model. EJB QL includes path expressions that enable navigation over relationships defined for entity beans and dependent objects.

OracleAS TopLink enables you to use EJB QL to define both queries that return Java objects and finders that return EJBs.

For more information, see "EJB QL".

Custom SQL

SQL is a standard query language enables you to request information from a database. The use of a native query language such as SQL is complex, but it offers advantages unavailable with other querying options.

For more information, see "Custom SQL".

Query Configuration Options

OracleAS TopLink queries offer several configuration options to customize query execution, cache usage, and performance.

Query Execution Options

The following query execution options enable you to optimize the way you collect and present query results.

Ordering

You can specify an order for the results of a query.

For more information, see "Ordering for Read All Queries".

Collection Types

By default, a query that returns a collection of objects presents the objects in a vector. You can specify that the collection be returned in any collection class that implements the Collection or Map interface (for example: HashMap).

For more information, see "Collection Classes".

Maximum Rows

You can set a maximum row size on any read query to limit the size of the result set. Use this to manage queries that can return an excessive number of objects.

For more information, see "Maximum Rows Returned" .

Timeouts

You can set the maximum amount of time that OracleAS TopLink waits for results from a query. This forces a hung or lengthy query to abort after the specified time has elapsed.

For more information, see "Query Timeout".

Query and the Cache

When you execute a query, OracleAS TopLink retrieves the information from either the database or the OracleAS TopLink session cache. You can configure the way queries use the OracleAS TopLink cache to optimize performance.

Refresh

Refresh the cache to update all objects in the cache with information from the database. This ensures that all objects in the cache are current.

For more information, see "Refresh".

In-Memory Querying

An in-memory query is a query that is run against the shared session cache. Careful configuration of in-memory querying improves performance, but not all queries benefit from in-memory querying. For example, queries for individual objects based on primary keys generally see performance gains from in-memory querying; queries based on non-primary keys are less likely to benefit.

By default, queries that look for a single object based on primary keys attempt to retrieve the required object from the cache first and then search the database if the object is not in the cache. All other query types search the database first, by default. You can specify whether a given query runs against the in-memory cache, the database, or both.

For more information, see "In-Memory Query Cache Usage".

Caching Results

By default, OracleAS TopLink stores query results in the session cache enabling OracleAS TopLink to execute the query repeatedly, without accessing the database. This is useful when you execute queries that run against static data.

In that it does not know how many objects it is looking. by default a read all query always goes to the database. However if the object already exists in the cache, time is saved by not having to build a new object from the row.

For more information, see "Caching Query Results".

Holding Results in the Query

You can configure a query to maintain an internal cache of the objects returned by the query. This internal cache is disabled by default.

For more information, see "Cache Results In Query Objects".

Performance

OracleAS TopLink offers several query options to improve performance, including the following:

Unit of Work

Queries that write to the database are often executed within a Unit of Work. You can also execute read queries within a Unit of Work, although reading the database this way is not common. There are two key configuration options available when you query within the Unit of Work:

Query Development Options

There are two ways to build OracleAS TopLink queries: you can use the OracleAS TopLink Mapping Workbench, or you can build them in code using the OracleAS TopLink API.

Building Queries with the OracleAS TopLink Mapping Workbench

The OracleAS TopLink Mapping Workbench Query tab supports OracleAS TopLink expressions, EJB QL queries and finders, and custom SQL queries and finders.

For more information, see "Specifying Named Queries and Finders" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Building Queries in Java

As with the OracleAS TopLink Mapping Workbench, the OracleAS TopLink query API supports OracleAS TopLink expressions, EJB QL queries and finders, and custom SQL queries and finders. However, if you require more options than are offered by these selection criteria types, you can create queries using the OracleAS TopLink query API to leverage OracleAS TopLink support for query by example and stored procedures.

For more information about the OracleAS TopLink query API, see the Oracle Application Server TopLink API Reference.

Using Predefined Queries

An effective way to implement queries is to build predefined queries that you store as part of the project descriptor file. OracleAS TopLink loads the queries into the application at runtime.

OracleAS TopLink supports the following predefined queries:

For more information, see "Predefined Queries".

Using Named Queries

Named queries are complete, self-contained queries stored in the project descriptor file. Using named queries improves your application performance because it reduces the resources required to run a query.

Building Named Queries with the OracleAS TopLink Mapping Workbench

You can create queries in the OracleAS TopLink Mapping Workbench using the OracleAS TopLink Mapping Workbench Query tab. The queries you build in the Query tab become part of the OracleAS TopLink project: OracleAS TopLink exports them automatically when you create deployment files from the project.

For more information, see "Specifying Named Queries and Finders" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Building Named Queries in Java

The OracleAS TopLink query API enables you to build queries outside of the OracleAS TopLink Mapping Workbench. However, unlike queries built in the OracleAS TopLink Mapping Workbench, OracleAS TopLink does not include these queries automatically in your OracleAS TopLink application. Instead, add them to the application manually, using after load methods to amend the project descriptor.

For more information about after load methods, see "Customizing OracleAS TopLink Descriptors with Amendment Methods".

Using Redirect Queries

Although most OracleAS TopLink queries search for objects directly, a redirect query generally invokes a method that exists on another class and waits for the results of the remote query. Redirect queries enable you to build and use complex operations, including operations that might not otherwise be possible within the query framework.

For more information, see "Redirect Queries".

Building EJB Finders

An EJB finder is a query as defined by the EJB specification. It returns EJBs, collections, and enumerations. The difference between a finder and a query is that queries return Java objects, but finders return EJBs. The OracleAS TopLink query framework enables you to create and execute complex finders that retrieve entity beans.

Finders contain finder methods that define search criteria. The work involved in creating these methods depends on whether you are building container-managed persistence (CMP) bean finders or bean managed persistence (BMP) bean finders:

In either case, you define finders in the Home interface of the bean.

For more information, see "EJB Finders".

Query Keys

A query key is an alias that OracleAS TopLink expressions use to relate to the descriptors and mappings for a given class. The query key is generally the name of an attribute of the class.

For example, consider a database table that includes a column called F_NAME that represents the attribute firstName in the class. Both represent the concept of an object's first name. OracleAS TopLink expressions use a query key to relate the two when you query on the database using the firstName as a selection criteria.

By default, OracleAS TopLink builds a query key in a descriptor for each attribute you map and automatically creates query keys for all mapped attributes of a class. The default name of the query key is the same as the name of the mapping.You can add additional query keys for nonmapped or duplicate purpose fields, either in Java code or using the OracleAS TopLink Mapping Workbench.

For more information, see "Working with Query Keys" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Query Building Basics

OracleAS TopLink supports several options for creating queries, including:

Expressions

OracleAS TopLink expressions enable you to specify query search criteria based on the object model. OracleAS TopLink translates the resulting query into SQL and converts the results of the query into objects. OracleAS TopLink provides two public classes to support expression:

Accessing Methods in Expressions

The OracleAS TopLink expression framework provides methods through the following classes:

The following code examples illustrate the two classes. Example 6-1 uses the Expression class, while Example 6-2 uses the ExpressionMath class.

Example 6-1 Using the Expression Class

expressionBuilder.get("lastName").equal("Smith");

Example 6-2 Using the ExpressionMath Class

ExpressionMath.abs(ExpressionMath.subtract(emp.get("salary"),
emp.get("spouse").get("salary")).greaterThan(10000)

This division of functionality enables OracleAS TopLink expressions to provide similar mathematical functionality to the Java class, java.lang.Math, but keeps both the Expression and ExpressionMath classes from becoming unnecessarily complex.

Expression Components

A simple expression normally consists of three parts:

In the following code fragment:

expressionBuilder.get("lastName").equal("Smith"); 

The expressionBuilder substitutes for the object or objects to be read from the database. In this example, expressionBuilder represents employees.

Expressions Compared to SQL

Expressions offer the following advantages over SQL when you access a database:

Boolean Logic

Expressions use standard boolean operators, such as AND, OR, and NOT and you can combine multiple expressions to form more complex expressions. For example, the following code fragment queries for projects managed by a selected person, with a budget greater than or equal to 1,000,000.

ExpressionBuilder project = new ExpressionBuilder();
Expression hasRightLeader, bigBudget, complex;
Employee selectedEmp = someWindow.getSelectedEmployee();
hasRightLeader = project.get("teamLeader").equal(selectedEmp);
bigBudget = project.get("budget").greaterThanEqual(1000000);
complex = hasRightLeader.and(bigBudget);
Vector projects = session.readAllObjects(Project.class, complex);

Database Functions

OracleAS TopLink supports the following database functions and operators:

Database functions allow you to define more flexible queries. For example, the following code fragment matches several last names, including "SMART", "Smith", and "Smothers":

emp.get("lastName").toUpperCase().like("SM%")

You access most functions through methods such as toUpperCase on the Expression class.

Mathematical Functions

Mathematical functions are available through the ExpressionMath class. Mathematical function support in expressions is similar to the support provided by the Java class java.lang.Math.

For example:

ExpressionMath.abs(ExpressionMath.subtract(emp.get("salary"),emp.get("spouse")
  .get("salary")).greaterThan(10000)

Platform and User Defined Functions

You can use expressions to implement database functions that OracleAS TopLink does not support directly. For simple functions, use the getFunction() operation, which the argument is the name of a function. For example, consider the following expression, which calls a function called VacationCredit on the database:

emp.get("lastName").getFunction("VacationCredit").greaterThan(42)

This expression produces the following SQL:

SELECT . . . WHERE VacationCredit(EMP.LASTNAME) > 42

You can also create more complex functions and add them to OracleAS TopLink. See "Platform and User-Defined Functions".

Expressions for One-to-One and Aggregate Object Relationships

Expressions can include an attribute that has a one-to-one relationship with another persistent class. A one-to-one relation translates naturally into a SQL join that returns a single row.

For example, the following code fragment accesses fields from an employee's address:

emp.get("address").get("country").like("S%")

This example corresponds to joining the EMPLOYEE table to the ADDRESS table, based on the address foreign key, and checking for the country name. You can nest these relationships infinitely, so it is possible to ask for complex information as follows:

project.get("teamLeader").get("manager").get("manager").get("address").get("street")

Expressions for Complex Relationships

You can query against complex relationships, such as one-to-many, many-to-many, direct collection, and aggregate collection relationships. Expressions for these types of relationships are more complex to build, because the relationships do not map directly to joins that yield a single row per object.

To query across a one-to-many or many-to-many relationship, use the anyOf operation. As its name suggests, this operation supports queries that return all items on the "many" side of the relationship that satisfy the query criteria. For example, consider the following code fragment:

emp.anyOf("managedEmployees").get("salary").lessThan(10000);

This code returns employees who manage at least one employee (through a one-to-many relationship) with a salary below $10,000. You can query across a many-to-many relationship using a similar strategy:

emp.anyOf("projects").equal(someProject)

OracleAS TopLink translates these queries to SQL, and SQL joins the relevant tables using a DISTINCT clause to remove duplicates.

For example:

SELECT DISTINCT . . . FROM EMP t1, EMP t2 WHERE
t2.MANAGER_ID = t1.EMP_ID AND t2.SALARY < 10000

Creating Expressions with the Expression Builder

To create Expression objects, use the get() method or its related methods on an Expression or ExpressionBuilder. The ExpressionBuilder acts as a stand-in for the objects you query. To construct a query, send messages to the ExpressionBuilder that correspond to the attributes of the objects. We recommend that you name ExpressionBuilder objects according to the type of objects against which you do a query.


Note:

An instance of ExpressionBuilder is specific to a particular query. Do not attempt to build another query using an existing builder, because it still contains information related to the first query.


Example 6-3 A Simple Expression Builder Expression

This example uses the query key lastName to reference the field name L_NAME.

Expression expression = new ExpressionBuilder().get("lastName").equal("Young");

Example 6-4 An Expression Using the and() Method

ExpressionBuilder emp = new ExpressionBuilder();
    Expression exp1, exp2;
    exp1 = emp.get("firstName").equal("Ken");
    exp2 = emp.get("lastName").equal("Young");
    return exp1.and(exp2);

Example 6-5 An Expression Using the notLike() Method

Expression expression = new ExpressionBuilder().get("lastName").notLike("%ung");

Using Multiple Expressions

Expressions support subqueries (SQL subselects) and parallel selects. To create a subquery, use a single expression builder. With parallel selects, use multiple expression builders when you define a single query. This enables you to specify joins for unrelated objects at the object level.

Subselects and Subqueries

Some queries compare the results of other, contained queries (or subqueries). SQL supports this comparison through subselects. OracleAS TopLink expressions provide subqueries to support subselects.

Subqueries enable you to define sophisticated expressions that query on aggregated values (counts, min, max) and unrelated objects (exists, in, comparisons). To obtain a subquery, pass an instance of a report query to any expression comparison operation, or use the subQuery operation on expression builder. The subquery is not required to have the same reference class as the parent query, and it must use its own expression builder.

You can nest subqueries, or use them in parallel. Subqueries can also make use of custom SQL.

For expression comparison operations that accept a single value (equal, greaterThan, lessThan), the subquery result must return a single value. For expression comparison operations that accept a set of values (in, exists), the subquery result must return a set of values.

Example 6-6 A Subquery Expression Using a Comparison and Count Operation

This example searches for all employees with more than 5 managed employees.

ExpressionBuilder emp = new ExpressionBuilder();
ExpressionBuilder managedEmp = new ExpressionBuilder();
ReportQuery subQuery =new ReportQuery(Employee.class, managedEmp);
subQuery.addCount();
subQuery.setSelectionCriteria(managedEmp.get("manager") .equal(emp));
Expression exp = emp.subQuery(subQuery).greaterThan(5);

Example 6-7 A Subquery Expression Using a Comparison and Max Operation

This example searches for the employee with the highest salary in the city of Ottawa.

ExpressionBuilder emp = new ExpressionBuilder();
ExpressionBuilder ottawaEmp = new ExpressionBuilder();
ReportQuery subQuery = new ReportQuery(Employee.class, ottawaEmp);
subQuery.addMax("salary");
subQuery.setSelectionCriteria(ottawaEmp.get("address").get("city").equal("Ottawa"));
Expression exp = 
  emp.get("salary").equal(subQuery).and(emp.get("address").get("city").equal("Ottawa"));

Example 6-8 A Subquery Expression Using a Not Exists Operation

This example searches for all employees that have no projects.

ExpressionBuilder emp = new ExpressionBuilder();
ExpressionBuilder proj = new ExpressionBuilder();
ReportQuery subQuery = new ReportQuery(Project.class, proj);
subQuery.addAttribute("id");
subQuery.setSelectionCriteria(proj.equal(emp.anyOf("projects"));
Expression exp = emp.notExists(subQuery);

Parallel Expressions

Parallel expressions enable you to compare unrelated objects. Parallel expressions require multiple expression builders, but do not require the use of report queries. Each expression must have its own expression builder, and you must use the constructor for expression builder that takes a class as an argument. The class does not have to be the same for the parallel expressions, and you can create multiple parallel expressions in a single query.

Only one of the expression builders is considered the primary expression builder for the query. This primary builder makes use of the zero argument expression constructor, and OracleAS TopLink obtains its class from the query.

Example 6-9 A Parallel Expression on Two Independent Employees

This example queries all employees with the same last name as another employee of different gender, and accounts for the possibility that returned results can be a spouse.

ExpressionBuilder emp = new ExpressionBuilder();
ExpressionBuilder spouse = new ExpressionBuilder(Employee.class);
Expression exp = emp.get("lastName").equal(spouse.get("lastName"))
  .and(emp.get("gender").notEqual(spouse.get("gender"));

Parameterized Expressions and Finders

A relationship mapping differs from a regular query because it retrieves data for many different objects. To enable you to specify these queries, supply arguments when you execute the query. Use the getParameter() and getField() methods to acquire values for the arguments.

A parameterized expression executes searches and comparisons based on variables instead of constants. This approach enables you to build expressions that retrieve context-sensitive information. This technique is useful when you:

Parameterized expressions require that the relationship mapping know how to retrieve an object or collection of objects based on its current context. For example, a one-to-one mapping from Employee to Address must query the database for an address based on foreign key information from the Employee table. Each mapping contains a query that OracleAS TopLink constructs automatically based on the information provided in the mapping. To specify expressions yourself, use the mapping customization mechanisms. For more information about the mapping customization mechanisms, see the Oracle Application Server TopLink Mapping Workbench User's Guide.

Expression getParameter()

The getParameter() method returns an expression that becomes a parameter in the query. This method enables you to create a query that employs user input as the search criteria. The parameter must be either the fully qualified name of the field from a descriptor's row, or a generic name for the argument.

Parameters you construct this way are global to the current query, so you can send this message to any expression object.

Example 6-10 Using Expression getParameter() and getField()

ExpressionBuilder address = new ExpressionBuilder();
Expression exp = address.getField
  ("ADDRESS.EMP_ID").equal(address.getParameter("EMPLOYEE.EMP_ID"));
exp = exp.and(address.getField("ADDRESS.TYPE").equal(null));

Expression getField()

The getField() method returns an expression that represents a database field with the given name. Use the Expression getField() method to construct the selection criteria for a mapping. The argument is the fully qualified name of the required field. Because fields are not global to the current query, you must send this method to an expression that represents the table from which this field is derived. See also "Data Queries" .

Example 6-11 The Use of a Parameterized Expression in a Mapping

This example obtains a simple one-to-many mapping from class PolicyHolder to Policy using a nondefault selection criteria. The SSN field of the POLICY table is a foreign key to the SSN field of the HOLDER table.

OneToManyMapping mapping = new OneToManyMapping();
mapping.setAttributeName("policies");
mapping.setGetMethodName("getPolicies");
mapping.setSetMethodName("setPolicies");
mapping.setReferenceClass(Policy.class);

// Build a custom expression here rather than using the defaults
ExpressionBuilder policy = new ExpressionBuilder();
mapping.setSelectionCriteria(policy.getField("POLICY.SSN")).equal(policy.
  getParameter("HOLDER.SSN")));

Example 6-12 A Parameterized Expression in a Custom Query

This example uses an employee's first name to demonstrate how to use a custom query to find the employee.

ExpressionBuilder emp = new ExpressionBuilder();
Expression firstNameExpression;
firstNameExpression = emp.get("firstName").equal(emp.getParameter("firstName"));
ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(Employee.class);
query.setSelectionCriteria(firstNameExpression);
query.addArgument("firstName");
Vector v = new Vector();
v.addElement("Sarah");
Employee e = (Employee) session.executeQuery(query, v);

Example 6-13 Nested Parameterized Expressions

This example demonstrates how to use a custom query to find all employees that live in the same city as a given employee.

ExpressionBuilder emp = new ExpressionBuilder();
Expression addressExpression;
addressExpression = 
emp.get("address").get("city").equal(emp.getParameter("employee").get("address")
.get("city"));
ReadObjectQuery query = new ReadObjectQuery(Employee.class);
query.setName("findByCity");
query.setReferenceClass(Employee.class);
query.setSelectionCriteria(addressExpression);
query.addArgument("employee");
Vector v = new Vector();
v.addElement(employee);
Employee e = (Employee) session.executeQuery(query, v);

Platform and User-Defined Functions

Different databases sometimes implement the same functions in different ways. For example, an argument that specifies that data returns in ascending order might be ASC or ASCENDING. To manage differences, OracleAS TopLink recognizes functions and other operators that vary according to the relational database.

Although most platform-specific operators exist in OracleAS TopLink, use the ExpressionOperator class to add your own.

An ExpressionOperator has a selector and a vector of strings:

You can also specify whether the operator is prefix or postfix. In a prefix operator, the first constant string prints before the first argument; in a postfix, it prints afterwards.

Example 6-14 Creating a New Expression Operator--The toUpperCase Operator

ExpressionOperator toUpper = new ExpressionOperator();
toUpper.setSelector();
Vector v = new Vector();
v.addElement("UPPER(");
v.addElement(")");
toUpper.printAs(v);
toUpper.bePrefix();
toUpper.setNodeClass(FunctionExpression.class); 

// To add this operator for all database
ExpressionOperator.addOperator(toUpper);
// To add to a specific platform
DatabasePlatform platform = session.getLogin().getPlatform();
platform.addOperator(toUpper);

Example 6-15 Accessing a User-Defined Function

This example illustrates the getFunction() method, called with a vector of arguments.

ReadObjectQuery query = new ReadObjectQuery(Employee.class);
expression functionExpression = new
  ExpressionBuilder().get("firstName").getFunction(ExpressionOperator.toUpper).
  equal("BOB");
query.setSelectionCriteria(functionExpression);
session.executeQuery(query);

Data Queries

You can use expressions to retrieve data rather than objects. This is a common approach when you work with unmapped information in the database, such as foreign keys and version fields.

Expressions that query for objects generally refer to object attributes, which may in turn refer to other objects. Data expressions refer to tables and their fields. You can combine data expressions and object expressions within a single query. OracleAS TopLink provides two main operators for expressions that query for data: getField(), and getTable().

getField()

The getField() operator enables you to retrieve data from either an unmapped table or an unmapped field from an object. In either case, the field must be part of a table represented by that object's class; otherwise, OracleAS TopLink raises an exception when you execute the query.

You can also use the getField() operator to retrieve the foreign key information for an object.

Example 6-16 Using getField Against an Object

builder.getField("[FIELD_NAME]").greaterThan("[ARGUMENT]"); 

getTable()

The getTable() operator returns an expression that represents an unmapped table in the database. This expression provides a context from which to retrieve an unmapped field when you use the getField() operator.

Example 6-17 Using getTable() and getField() Together

builder.getTable("[TABLE_NAME]").getField("[FIELD_NAME]").equal("[ARGUMENT]");

A common use for the getTable() and getField() operators is to retrieve information from a link table (or reference table) that supports a many-to-many relationship. Example 6-18 reads a many-to-many relationship that uses a link table and also checks an additional field in the link table. This code combines an object query with a data query, using the employee's manager as the basis for the data query. It also features parameterization for the project ID.

Example 6-18 Using a Data Query Against a Link Table

ExpressionBuilder emp = new ExpressionBuilder();
Expression manager = emp.get("manager"); 
Expression linkTable = manager.getTable("PROJ_EMP");
Expression empToLink = emp.getField("EMPLOYEE
  .EMP_ID").equal(linkTable.getField("PROJ_EMP.EMP_ID"); Expression projToLink = linkTable.getField("PROJ_EMP   .PROJ_ID").equal(emp.getParameter("PROJECT.PROJ_ID")); Expression extra = linkTable.getField("PROJ_EMP.TYPE").equal("W"); query.setSelectionCriteria((empToLink.and(projToLink)).and(extra));

Query Keys

A query key is an alias for a field name. Instead of referring to a field using a DBMS-specific field name such as F_NAME, query keys allow OracleAS TopLink expressions to refer to the field using class attribute names such as firstName. This offers the following advantages:

For more information about query keys with the OracleAS TopLink Mapping Workbench, see "Working with Query Keys," in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Automatically-Generated Query Keys

OracleAS TopLink defines direct query keys for all direct mappings and has a special query key type for each mapping. You can use query keys to access fields that do not have direct mappings associated with them, such as the version field used for optimistic locking or the type field used for inheritance.

Example 6-19 Automatically-Generated Query Key in the OracleAS TopLink Expression Framework

Vector employees = session.readAllObjects(Employee.class,
  new ExpressionBuilder().get("firstName").equal("Bob"));

Relationship Query Keys

OracleAS TopLink supports and defines query keys for relationship mappings. You can use query keys to join across a relationship. One-to-one query keys define a joining relationship. To access query keys for relationship mappings, use the get() method in expressions.

Example 6-20 One-to-One Query Key

The following code example illustrates how to use a one-to-one query key within the OracleAS TopLink expression framework.

ExpressionBuilder employee = new ExpressionBuilder();
Vector employees = session.readAllObjects(Employee.class,
  employee.get("address").get("city").equal("Ottawa"));

To access one-to-many and many-to-many query keys that define a distinct join across a collection relationship, use the anyOf() method in expressions.

If no mapping exists for the relationship, you can also define relationship query keys manually. Relationship query keys are not supported directly by the OracleAS TopLink Mapping Workbench. To define a relationship query key, specify and write an amendment method, and use the addQueryKey() message to register the query keys.

Example 6-21 Defining One-to-One Query Key Example

The following code defines a one-to-one query key.

/* Static amendment method in Address class, addresses do not know their owners 
in the object-model, however you can still query on their owner if a 
user-defined query key is defined */
public static void addToDescriptor(Descriptor descriptor)
{
OneToOneQueryKey ownerQueryKey = new OneToOneQueryKey();
ownerQueryKey.setName("owner");
ownerQueryKey.setReferenceClass(Employee.class);
ExpressionBuilder builder = new ExpressionBuilder();
ownerQueryKey.setJoinCriteria(builder.getField("EMPLOYEE.ADDRESS_
ID").equal(builder.getParameter("ADDRESS.ADDRESS_ID")));
descriptor.addQueryKey(ownerQueryKey);
}

Reference

Table 6-1 and Table 6-2 summarize the most common public methods for ExpressionBuilder and Expression. For more information about the available methods for ExpressionBuilder and Expression, see the Oracle Application Server TopLink API Reference.

Table 6-1 Elements for Expression Builder  
Element Method Name

Constructors

ExpressionBuilder()
ExpressionBuilder(Class aClass)

Expression creation methods

get(String queryKeyName)
getAllowingNull(String queryKeyName)
anyOf(String queryKeyName)
anyOfAllowingNone(String queryKeyName)
getField(String fieldName)
in(ReportQuery subQuery)
Table 6-2 Elements for Expression  
Element Method Name

Constructors

Never use the Expression constructors. Always use an ExpressionBuilder to create a new expression.

Expression operators

equal(Object object)
notEqual(Object object)
greaterThan(Object object)
lessThan(Object object)
isNull()
notNull()

Logical operators

and(Expression theExpression)
not()
or(Expression theExpression)

Key word searching

equalsIgnoreCase(String theValue)
likeIgnoreCase(String theValue)

Aggregate functions (for use with report query)

minimum()
maximum()

Relationship operators

anyOf(String queryKeyName)
anyOfAllowingNone(String queryKeyName)
get(String queryKeyName)
getAllowingNull(String queryKeyName)
getField(String fieldName)

Custom SQL

The expression framework enables you to define complex queries at the object level. If your application requires a more complex query, use SQL or stored procedure calls to create custom database operations.

For more information about stored procedure calls, see "Stored Procedure Calls".

SQL Queries

You can provide a SQL string to any query instead of an expression, but the SQL string must return all data required to build an instance of the queried class. The SQL string can be a complex SQL query or a stored procedure call.

You can invoke SQL queries through the session read methods or through a read query instance.

Example 6-22 A Session Read Object Call Query With Custom SQL

Employee employee = (Employee) session.readObjectCall(Employee.class), new 
SQLCall("SELECT * FROM EMPLOYEE WHERE EMP_ID = 44");

Example 6-23 A Session Method with Custom SQL

This example queries user and time information.

Vector rows = session.executeSelectingCall(new SQLCall("SELECT USER, SYSDATE FROM DUAL"));

SQL Data Queries

OracleAS TopLink offers the following data-level queries to read or modify data (but not objects) in the database:

Example 6-24 A Direct Read Query with SQL

This example uses SQL to read all employee IDs.

DirectReadQuery query = new DirectReadQuery();
query.setSQLString("SELECT EMP_ID FROM EMPLOYEE");
Vector ids = (Vector) session.executeQuery(query);

Example 6-25 A Data Modify Query with SQL

This example uses SQL to switch the database.

DataModifyQuery query = new DataModifyQuery();
query.setSQLString("USE SALESDATABASE");
session.executeQuery(query);

Stored Procedure Calls

You can provide a StoredProcedureCall object to any query instead of an expression or SQL string, but the procedure must return all data required to build an instance of the class you query.

Example 6-26 A Read All Query With a Stored Procedure

ReadAllQuery readAllQuery = new ReadAllQuery();
call = new StoredProcedureCall();
call.setProcedureName("Read_All_Employees");
call.useNamedCursorOutputAsResultSet("RESULT_CURSOR");
readAllQuery.setCall(call);
Vector employees = (Vector) session.executeQuery(readAllQuery);

Output Parameters

The StoredProcedureCall object allows you to use output parameters. Output parameters enable the stored procedure to return additional information. You can use output parameters to define a readObjectQuery if they return the fields required to build the object.


Note:

Not all databases support the use of output parameters to return data. However, because these databases generally support returning result sets from stored procedures, they do not require output parameters.


Example 6-27 Stored Procedure Call with an Output Parameter

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("CHECK_VALID_POSTAL_CODE");
call.addNamedArgument("POSTAL_CODE");
call.addNamedOutputArgument("IS_VALID");
ValueReadQuery query = new ValueReadQuery();
query.setCall(call);
query.addArgument("POSTAL_CODE");
Vector parameters = new Vector();
parameters.addElement("L5J1H5");
Number isValid = (Number) session.executeQuery(query,parameters);

Cursor Output Parameters

Oracle databases use output parameters rather than result sets to return data from stored procedures. Cursored output parameters enable you to retrieve the result set in a cursored stream rather than as a single result set. When you use the Oracle JDBC drivers, configure a StoredProcedureCall object to pass a cursor to OracleAS TopLink as a normal result set.

Example 6-28 Stored Procedure with a Cursored Output Parameter

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("READ_ALL_EMPLOYEES");
call.useNamedCursorOutputAsResultSet("RESULT_CURSOR");
ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.setCall(call);
Vector employees = (Vector) Session.executequery(Query);

For more information about cursored streams, see "Java Streams".

Output Parameter Event

OracleAS TopLink manages output parameter events for databases that support them. For example, if a stored procedure returns an error code that indicates that the application wants to check for an error condition, OracleAS TopLink raises the session event OutputParametersDetected to allow the application to process the output parameters.

Example 6-29 Stored Procedure with Reset Set and Output Parameter Error Code

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("READ_EMPLOYEE");
call.addNamedArgument("EMP_ID");
call.addNamedOutputArgument("ERROR_CODE");
ReadObjectQuery query = new ReadObjectQuery();
query.setCall(call);
query.addArgument("EMP_ID");
ErrorCodeListener listener = new ErrorCodeListener();
session.getEventManager().addListener(listener);
Vector args = new Vector();
args.addElement(new Integer(44));
Employee employee = (Employee) session.executeQuery(query, args);

Reference

Table 6-3 summarizes the most common public methods for the StoredProcedureCall. For more information about the available methods for the StoredProcedureCall, see the Oracle Application Server TopLink API Reference.

Table 6-3 Elements for Stored Procedure Call  
Element Method Name

Selection specification

setProcedureName(String name)

Input parameters

addNamedArgument(String name)
addNamedArgument(String dbName, String javaName)
addNamedArgumentValue(String dbName, Object value)
addUnnamedArgument(String javaName)
addUnnamedArgumentValue(Object value)

Input/Output parameters

addNamedInOutputArgument(String name)
addNamedInOutputArgument(String dbName, String javaName, 
String javaName, Class type)
addNamedInOutputArgumentValue(String dbName, Object 
value, String javaName, Class type)
public void addUnnamedInOutputArgument(String 
inArgumentFieldName, String 
outArgumentFieldName, Class type)
public void addUnnamedInOutputArgumentValue(Object 
inArgumentValue, String outArgumentFieldName, Class 
type)

Output parameters

addNamedOutputArgument(String name)
addNamedOutputArgument(String dbName, String javaName)
addNamedOutputArgument(String dbName, String javaName, 
Class javaType)
addUnnamedOutputArgument(String javaName)
public void addunnamedOutputArgument(String 
argumentFieldName, Class type)

Cursor output parameters

useNamedCursorOutputAsResultSet(String argumentName)
useUnnamedCursorOutputAsResultSet()

EJB QL

EJB QL is a query language that is similar to SQL, but differs because it presents queries from an object model perspective and includes path expressions that enable navigation over the relationships defined for entity beans and dependent objects. Although EJB QL is usually associated with Enterprise JavaBeans (EJBs), OracleAS TopLink enables you to use EJB QL with regular Java objects as well. In OracleAS TopLink, EJB QL enables users to declare queries, using the attributes of each abstract entity bean in the object model. This offers the following advantages:

Using EJB QL with OracleAS TopLink

OracleAS TopLink support for EJB QL enables you to:

For more information about EJB QL queries with the OracleAS TopLink Mapping Workbench, see the Oracle Application Server TopLink Mapping Workbench User's Guide.

ReadAllQuery

The basic API for a ReadAll query with EJB QL is as follows:

setEJBQLString("...")

Provide either a SELECT clause or a reference class, and execute the query normally.

Example 6-30 A Simple ReadAllQuery Using EJB QL

ReadAllQuery theQuery = new ReadAllQuery();
theQuery.setReferenceClass(EmployeeBean.class);
theQuery.setEJBQLString("SELECT OBJECT(emp) FROM EmployeeBean emp");
...
Vector returnedObjects = (Vector)aSession.executeQuery(theQuery);

Example 6-31 A Simple ReadAllQuery Using EJB QL and Passing Arguments

This example defines the query similarly to Example 6-30, but creates, fills, and passes a vector of arguments to the executeQuery method.

// First define the query
ReadAllQuery theQuery = new ReadAllQuery();
theQuery.setReferenceClass(EmployeeBean.class);
theQuery.setEJBQLString("SELECT OBJECT(emp) FROM EmployeeBean emp WHERE 
emp.firstName = ?1");
...
// Next define the Arguments
Vector theArguments = new Vector();
theArguments.add("Bob");
...
// Finally execute the query passing in the arguments
Vector returnedObjects = (Vector)aSession.executeQuery(theQuery, theArguments);

Session

You can execute EJB QL directly against the session. This returns a vector of the objects specified by the reference class. The basic API is:

aSession.readAllObjects(<ReferenceClass>, <EJBQLCall>)
/* <ReferenceClass> is the return class type and <EJBQLCall> is the EJBQL string 
to be executed */
// Call ReadAllObjects on a session.
Vector theObjects = (Vector)aSession.readAllObjects(EmployeeBean.class, new 
EJBQLCall( "SELECT OBJECT (emp) from EmployeeBean emp");

EJB QL Limitations

OracleAS TopLink supports all the EJB QL specification with the following exceptions:

Query by Example

Query by example enables you to specify queries when you provide sample instances of the persistent objects to be queried.

To define a query by example, provide a ReadObjectQuery or a ReadAllQuery with a sample persistent object instance and an optional query by example policy. The sample instance contains the data to query, and the query by example policy contains optional configuration settings, such as the operators to use and the attributes to consider or ignore.


Note:

Query by example is not available for EJB 2.0 beans.


Defining a Sample Instance

Query by example enables you to query on any attribute that uses a direct mapping or a one-to-one relationship (including those with nesting). It does not support other relationship mapping types.

By default, OracleAS TopLink ignores attributes in the sample instance that contain zero (0), empty strings, and FALSE. To modify the list of values, see "Defining a Query by Example Policy". You can use any valid constructor to create a sample instance or example object. Set only the attributes on which you base the query; set all other attributes to null.

Query by example uses the AND operator to tie the attribute comparisons together.

Example 6-32 Using Query by Example

This example queries the employee Bob Smith.

ReadObjectQuery query = new ReadObjectQuery();
Employee employee = new Employee();
employee.setFirstName("Bob");
employee.setLastName("Smith");
query.setExampleObject(employee);

Employee result = (Employee) session.executeQuery(query);

Example 6-33 Using Query by Example

This example queries across the employee's address.

ReadAllQuery query = new ReadAllQuery();
Employee employee = new Employee();
Address address = new Address();
address.setCity("Ottawa");
employee.setAddress(address);
query.setExampleObject(employee);

Vector results = (Vector) session.executeQuery(query);

Defining a Query by Example Policy

OracleAS TopLink support for query by example includes a query by example policy. You can edit the policy to modify query by example default behavior. You can modify the policy to:

To specify a query by example policy, include an instance of QueryByExamplePolicy with the query.

Example 6-34 Query by Example Policy Using Like

This example uses like for Strings and includes only objects whose salary is greater than zero.

ReadAllQuery query = new ReadAllQuery();
Employee employee = new Employee();
employee.setFirstName("B%");
employee.setLastName("S%");
employee.setSalary(0);
query.setExampleObject(employee);
/* Query by example policy section adds like and greaterThan */
QueryByExamplePolicy policy = new QueryByExamplePolicy();
policy.addSpecialOperation(String.class, "like");
policy.addSpecialOperation(Integer.class, "greaterThan");
policy.alwaysIncludeAttribute(Employee.class, "salary");
query.setQueryByExamplePolicy(policy);
Vector results = (Vector) session.executeQuery(query);

Example 6-35 Query by Example Policy Using Key Words

This example uses key words for Strings and ignores -1.

ReadAllQuery query = new ReadAllQuery();
Employee employee = new Employee();
employee.setFirstName("bob joe fred");
employee.setLastName("smith mc mac");
employee.setSalary(-1);
query.setExampleObject(employee);
/* Query by example policy section */
QueryByExamplePolicy policy = new QueryByExamplePolicy();
policy.addSpecialOperation(String.class, "containsAnyKeyWords");
policy.excludeValue(-1);
query.setQueryByExamplePolicy(policy);
Vector results = (Vector) session.executeQuery(query);

Combining Query by Example with Expressions

To create more complex query by example queries, combine query by example with OracleAS TopLink expressions.

Example 6-36 Combining Query by Example with Expressions

ReadAllQuery query = new ReadAllQuery();
Employee employee = new Employee();
employee.setFirstName("Bob");
employee.setLastName("Smith");
query.setExampleObject(employee);
/* This section specifies the expression */
ExpressionBuilder builder = new ExpressionBuilder();
query.setSelectionCriteria(builder.get("salary").between(100000,200000);
Vector results = (Vector) session.executeQuery(query);

Reference

Table 6-4 summarizes the most common public methods for QueryByExample. For more information about the available methods, see the Oracle Application Server TopLink API Reference.

Table 6-4 Elements for Query By Example Policy  
Element Method Name

Special operations

addSpecialOperation(Class theClass, String operation)

Forced inclusion

alwaysIncludeAttribute(java.lang.Class exampleClass, 
java.lang.String attributeName) 

includeAllValues()

Attribute exclusion

excludeValue(Object value)
excludeDefaultPrimitiveValues()

Null equality

setShouldUseEqualityForNulls(boolean flag) 

Executing Queries

OracleAS TopLink provides several options to execute queries, including:

Session Queries

The Session class and its subclasses (including DatabaseSession and UnitOfWork) provide methods to read, create, modify, and delete objects stored in a database. These methods, known as query methods, enable you to create queries against the object model. Session queries are easy to use and are flexible enough to perform most database operations.

The DatabaseSession class provides direct support to read and modify the database by offering read, write, insert, update, and delete operations.

The UnitOfWork class also provides methods to modify data. The Unit of Work is a safer approach to data modification than the DatabaseSession methods, because it isolates changes until they are complete. Whenever possible, use the Unit of Work to write or update rather than the write, insert, update, and delete methods available in the database session.

For more information, see "Unit of Work Basics".

Reading Objects from the Database

The session provides the following methods to access the database:

Read Operation

The readObject() method retrieves a single object from the database. The application must specify the class of object to read. If no object matches the criteria is found, null is returned.

For example, the basic read operation is:

session.readObject(MyDomainObject.class);

This example returns the first instance of MyDomainObject found in the table used for MyDomainObject. OracleAS TopLink provides the Expression class to specify querying parameters for a specific object.

When you search for a single, specific object using a primary key, the readObject() method is more efficient than the readAllObjects() method because readObject() can find an instance in the cache without accessing database. Because a readAllObjects() operation does not know how many objects match the criteria, it always searches the database to find matching objects, even if it finds matching objects in the cache.

Example 6-37 readObject() Using an Expression

import oracle.toplink.sessions.*;
import oracle.toplink.expressions.*;

/* Use an expression to read in the Employee whose last name is Smith. Create an 
expression using the Expression Builder and use it as the selection criterion of 
the search */
Employee employee = (Employee) session.readObject(Employee.class, new 
ExpressionBuilder().get("lastName").equal("Smith"));

Read All Operation

The readAllObjects() method retrieves a Vector of objects from the database and does not order the returned objects. If the query does not find any matching objects, it returns an empty Vector.

Specify the class for the query. You can also include an expression to define more complex search criteria, as illustrated in Example 6-38.

Example 6-38 readAllObjects() Using an Expression

// Returns a Vector of employees whose employee salary > 10000
Vector employees = session.readAllObjects(Employee.class,new 
ExpressionBuilder.get("salary").greaterThan(10000));

Refresh Operation

The refreshObject() method causes OracleAS TopLink to update the object in memory with data from the database. This operation refreshes any privately owned objects as well.


Note:

A privately owned object is one that cannot exist without its parent, or source object.


Writing Objects to the Database

The Unit of Work provides the safest mechanism for writing objects in most OracleAS TopLink applications. However, when you can safely write directly to the database (for example: in a single-user or a two-tier application), session methods are the most efficient database writing tool. Database session provides the following methods to write to a database:

Writing a Single Object to the Database

When you invoke the writeObject() method, the method performs a does-exist check to determine whether an object exists. If the object exists, writeObject() updates the object; if it does not exist, writeObject() inserts a new object.

The writeObject() method writes privately owned objects in the correct order to maintain referential integrity.

Call the writeObject() method when you cannot verify that an object exists on the database.

Example 6-39 Writing a Single Object Using writeObject()

//Create an instance of employee and write it to the database
Employee susan = new Employee();
susan.setName("Susan");
...
//Initialize the susan object with all other instance variables
session.writeObject(susan); 

Writing All Objects to the Database

You can call the writeAllObjects() method to write multiple objects to the database. The writeAllObjects() method performs the same does-exist check as the writeObject() method and then performs the appropriate insert or update operations.

Example 6-40 Writing Several Objects Using writeAllObjects()

// Read a Vector of all the current employees in the database.
Vector employees = (Vector) session.readAllObjects(Employee.class);
...//Modify any employee data as necessary
//Create a new employee and add it to the list of employees
Employee susan = new Employee();
...
//Initialize the new instance of employee
employees.add(susan);
/* Write all employees to the database. The new instance of susan which is not 
currently in the database will be inserted. All the other employees which are 
currently stored in the database will be updated */
session.writeAllObjects(employees);

Adding New Objects to the Database

The insertObject() method creates a new object on the database, but does not perform the does-exist check before it attempts the insert operation. The insertObject() method is more efficient than the writeObject() method if you are certain that the object does not yet exist on the database. If the object does exist, the database throws an exception when you execute the insertObject() call.

Modifying Existing Objects in the Database

The updateObject() method updates existing objects in the database, but does not perform the does-exist check before it attempts the update operation. The updateObject() is more efficient than the writeObject()method if you are certain that the object does exist in the database. If the object does not exist, the database throws an exception when you execute the updateObject() call.

Deleting Objects in the Database

To delete an OracleAS TopLink object from the database, read the object from the database and then call the deleteObject() method. This method deletes both the specified object and any privately owned data.

Query Objects

Query objects are the standard devices OracleAS TopLink uses to interact with the database. They support database commands such as create, read, update, and delete, and accept search criteria specified in several ways, including OracleAS TopLink expressions.

OracleAS TopLink provides you with direct access to query objects, which support more complex queries than the session query API. You can build custom query objects to improve application performance or to support complex queries. Use the custom query object classes you create with the session or a descriptor's query manager to:

The OracleAS TopLink Mapping Workbench provides graphical tools to create query objects. Although this section discusses query objects in the context of Java code, we recommend that you create query objects in the OracleAS TopLink Mapping Workbench.

Query Object Components

OracleAS TopLink uses query objects to store information about a database query. A complete query object stores information about:

Creating a Query Object

The following steps illustrate how to create a query object in Java code.

Step 1 Specify the query type to initialize the query object.

To execute a query, select one of the following query object classes:

To execute SQL expressions, use the following query object classes:

Step 2 Set the reference class.

The reference class specifies the class against which the query runs. Use the setReferenceClass() call to select a searchable class.

Step 3 For read queries, configure the query for execution.

To specify how a query executes, call one of the following the methods:

This setting is optional. If you do not specify read criteria, a ReadAllQuery returns every object of the reference class in the database, and a ReadObjectQuery returns the first object it encounters.

Step 4 Add query arguments.

You can pass arguments to the query object by calling addArgument() in addition to the executeQuery() method. Arguments describe the objects for the query to return.

Step 5 Register the query object with the session.

After initialization, use the addQuery() method to register the query object with the session. Name the query when you register it. The session then manages the query for you. This enables you to call the query by name.

Registering the query object with the session is optional. If you do not register the query object, specify the entire query every time you execute it, or manage it manually outside of the session.

Step 6 Execute the query.

To execute the query, use the executeQuery() call to call the object by name. As required, provide values for any defined arguments.

Read Query Object Examples

Although query objects support writing to a database, reading is their most common use. This section provides several examples that illustrate the use of query objects for reading the database.

Example 6-41 illustrates a simple read query. It uses an OracleAS TopLink expression, but does not use its own arguments for the query. Instead, it relies on the search parameters the expression provides. This example builds the expression within its code, but does not register the query with the session.

Example 6-41 A Simple ReadAllQuery

// This example returns a Vector of employees whose employee ID is > 100.

// Initialize the query object by specifying the query type
ReadAllQuery query = new ReadAllQuery();

//Set the reference class for the query.
query.setReferenceClass(Employee.class);

/* Configure the query execution. Because this example uses an expression, it 
uses the setSelectionCriteria call */
query.setSelectionCriteria(new ExpressionBuilder.get("id").greaterThan(100)); 

// Execute the query
Vector employees = (Vector) session.executeQuery(query);

Example 6-42 illustrates a complex readObject query that uses all available configuration options.

Example 6-42 A Named Read Query with Two Arguments

// Define two expressions that map to the first and last name of the employee.
ExpressionBuilder emp = new ExpressionBuilder();
Expression firstNameExpression = 
emp.get("firstName").equal(emp.getParameter("firstName"));
Expression lastNameExpression = emp.get("lastName").equal(emp.getParameter("lastName"));

//Initialize the query object by specifying the query type
ReadObjectQuery query = new ReadObjectQuery();
//Set the reference class for the query.
query.setReferenceClass(Employee.class);
/* Configure the query execution. Because this example uses an expression, it uses the 
setSelectionCriteria call */
query.setSelectionCriteria(firstNameExpression.and(lastNameExpression)); 
//Specify the required arguments for the query.
query.addArgument("firstName");
query.addArgument("lastName");

// Add the query to the session.
session.addQuery("getEmployeeWithName", query);

/* Execute the query by referencing its name and providing values for the specified 
arguments */
Employee employee = (Employee) session.executeQuery("getEmployeeWithName","Bob","Smith");

Specialized Query Object Options

In addition to the query object configuration options discussed in "Creating a Query Object", several more specialized options are available for customizing query objects

Ordering for Read All Queries

Ordering is a common option for query objects. To order the collection of objects returned from a ReadAllQuery, use the addOrdering(), addAscendingOrdering(), or addDescendingOrdering() methods. You can apply order based on attribute names or query keys and expressions.

Example 6-43 A Query with Simple Ordering

// Retrieves objects ordered by lastName then firstName in Ascending Order 
ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.addAscendingOrdering ("lastName");
query.addAscendingOrdering ("firstName");
Vector employees = (Vector) session.executeQuery(query);

Example 6-44 A Query with Complex Ordering

/* Retrieves objects ordered by Street Address, descending case-insensitive 
order of Cities, and manager's Last Name */
ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
ExpressionBuilder emp = new ExpressionBuilder();
query.addOrdering (emp.getAllowingNull("address").get("street"));
query.addOrdering 
(emp.getAllowingNull("address").get("city").toUpperCase().descending());
query.addOrdering(emp.getAllowingNull("manager").get("lastName"));
Vector employees = (Vector) session.executeQuery(query);

Note the use of getAllowingNull, which creates an outer join for the address and manager relationships. This ensures that employees without an address or manager still appear in the list.

For more information, see "Join Reading".

Parameterized SQL in Query Objects

To enable the parameterized SQL on individual queries, use the bindAllParameters() and cacheStatement() methods. This causes OracleAS TopLink to use a prepared statement, binding all SQL parameters and caching the prepared statement. When you re-execute this query, you avoid the SQL preparation, which improves performance.

For more information, see Chapter 10, "Tuning for Performance".


Note:

Do not use OracleAS TopLink's internal statement caching with an external connection pool.


Example 6-45 A Simple Read Query Object with Parameterized SQL

ReadObjectQuery query = new ReadObjectQuery(Employee.class);
query.setShouldBindAllParameters(true);
query.setShouldCacheStatement(true);

Collection Classes

By default, a ReadAllQuery returns its result objects in a vector. You can configure the query to return the results in any collection class that implements the Collection or Map interface.

Example 6-46 Specifying the Collection Class for a Collection

ReadAllQuery query = new ReadAllQuery(Employee.class);
query.useCollectionClass(LinkedList.class);
LinkedList employees = (LinkedList) getSession().executeQuery(query);

Example 6-47 Specifying the Collection Class for a Map

ReadAllQuery query = new ReadAllQuery(Employee.class);
query.useMapClass(HashMap.class, "getFirstName");
HashMap employees = (HashMap) getSession().executeQuery(query);

For more information about interfaces, see "Working with Interfaces" in the Oracle 
Application Server TopLink Mapping Workbench User's Guide.
Using Cursoring for a ReadAllQuery

The ReadAllQuery class includes methods for cursored stream and scrollable cursor support. If you expect the result set to be large, streams and cursors enable you to handle the result sets more efficiently.

For more information, see "Cursors and Streams".

Query Optimization

OracleAS TopLink supports both joins and batch reads to optimize database reads. When your query reads a large number of objects, these techniques dramatically decrease the number of times you need to access the database during a read operation. Use the addJoinedAttribute() and addBatchReadAttribute() methods to configure query optimization.

For more information, see "Query Object Performance Options", and Chapter 10, "Tuning for Performance".

Other options to optimize queries include the setMaxRows() method and partial object reading.

Maximum Rows Returned

You can limit a query to a specified maximum number of rows. Use this feature to avoid queries that can return an excessive number of objects.

To specify a maximum number of rows, use the setMaxRows method, and pass an integer that represents the maximum number of rows for the query.

Example 6-48 Setting the Maximum Returned Object Size

ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.setMaxRows(5);
Vector employees = (Vector) session.executeQuery(query);

The setMaxRows method limits the number of rows the query returns, but does not enable you to acquire more records after the initial result set. If you want to browse the result set in fixed increments, use either cursors or cursored streams.

For more information, see "Java Streams" .

Partial Object Reading

OracleAS TopLink enables you to query for partial objects. For example, you can create a read query that returns a subset of an object's attributes, rather than the entire object. This option improves read performance when the full object is not required. For example, use partial object reading to create a list of objects from which the client chooses the required object.

When you use partial object reading, be aware that:

Use the addPartialAttribute() method to configure partial object reading.

For more information, see "Query Object Performance Options", and Chapter 10, "Tuning for Performance".

Query Timeout

You can implement a timeout for query objects. This enables you to automatically abort a hung or lengthy query after the specified time elapses. OracleAS TopLink throws a DatabaseException after the timeout.

To specify a timeout, implement the setQueryTimeout() call and pass the timeout interval as an integer representing the number of seconds before timeout occurs.

Example 6-49 Timeout on Query Objects

// Create the appropriate query and set timeout limits
ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.setQueryTimeout(2);
try{
    Vector employees = (Vector)session.executeQuery(query);
} catch (DatabaseException ex) {
    // timeout occurs
}

Predefined Queries

Predefined queries enable you to create efficient, reusable queries. OracleAS TopLink creates predefined queries and registers them with a session or descriptor when the application starts. You can then retrieve the queries by name and execute them.

The most common way to create a predefined query is to register the query to a descriptor by specifying an amendment method with the OracleAS TopLink Mapping Workbench for an after load event.

Predefined queries improve the performance of frequently called queries because when you create a query, it is saved and reused as required. Each time you use a query, you create three or more objects that OracleAS TopLink uses to build the SQL statement. If you use predefined queries, OracleAS TopLink creates these objects only once, at binding time. OracleAS TopLink stores the queries as SQL statements in the descriptor and makes them available for the duration of the session.

In addition to performance improvements, predefined queries add structure to a querying framework and give you more options for reading query structure from alternative sources, such as XML.

Named Queries

Named queries improve application performance, because they reduce the resources required to run a query.

The readAllObjects(Class c, Expression e) creates a ReadAllQuery, which builds the other objects it needs to perform its task. After the you execute the readEmployeesMatchingLastName method, the query, expression, expressionBuilder, and any other related objects become garbage. Each time you call this method, OracleAS TopLink creates these related objects again, uses them once, and then discards them.

The use of named queries eliminates this behavior. To configure named queries, use a descriptor amendment method. This creates named queries when you open a database session.

Example 6-50 Named Query in the Descriptor File

public class MyTopLinkManager {
// some code that manages sessions, login, etc...
...
// This method is called by front end when needing to query on last names
   public Vector readEmployeesMatchingLastName(String theName) {
      ExpressionBuilder eBuilder = new ExpressionBuilder();
         Expression exp = eBuilder.get("lastName").like(theName+"%");
         return session.readAllObjects(Employee.class, exp);
     }
}

Use and Reuse

OracleAS TopLink stores named queries by name on a per descriptor basis. When the application needs a query, it calls the named query and passes the required arguments. Because OracleAS TopLink builds the query when it opens the database session, the query is immediately available. In addition, the query is named and bound to a descriptor, so it is reusable.

The first time you execute a named query, OracleAS TopLink calculates the core SQL based on your database platform and schema. OracleAS TopLink caches this information and reuses it if you reuse the query.

Centralized Query Management

OracleAS TopLink creates and registers named queries in a centralized location, usually your descriptor amendment method. Storing all queries in one location facilitates the reuse of queries and simplifies query maintenance.

When Not To Use Named Queries

Rarely used queries may be more efficient when built on an as-needed basis. If you seldom use a given query, it may not be worthwhile to build and store that query when you invoke a session.

Named Finders

A named finder is an OracleAS TopLink query registered with an EJB container under a specific name. When using named finders, the find method on the Home interface must correspond to the name of an OracleAS TopLink query registered with the container. To implement and register the query with the container, use an OracleAS TopLink descriptor amendment method or session amendment class.

Example 6-51 A Named Finder

/* The named finder in this example uses an OracleAS TopLink query named 
findCustomersInCity */
public Enumeration findCustomersInCity(String City)throws FinderException, 
RemoteException;

Before you build and implement the findCustomersInCity finder shown in Example 6-51, define the corresponding named query, and register it with the project descriptor. To build the named query, employ:

OracleAS TopLink Mapping Workbench Using EJB QL, SQL, or Expressions

Use EJB QL, SQL, or the OracleAS TopLink expression framework in the OracleAS TopLink Mapping Workbench to:

Java Code Using the OracleAS TopLink Expression Framework

Use the OracleAS TopLink expression framework to add the query employing a user defined method. Define these methods in one of the following ways:

Example 6-52 Define an Amendment Method

/* This example defines the findCustomersInCity query in the amendment method 
of the descriptor */
public static void amendment(Descriptor descriptor) {
// create a query...

descriptor.getQueryManager().addQuery("findCustomersInCity", query);

Example 6-53 Define a Pre-Login Event

/* This example defines the findCustomersInCity query in the preLogin method 
of a session event listener class and specifies the session event listener class 
in the toplink-ejb-jar.xml deployment descriptor */
public void preLogin(SessionEvent event) {
// create a query...
event.getSession().getDescriptor(Customer.class).getQueryManager().addQuery("fin
dCustomersInCity", query);
}

OracleAS TopLink Expression Framework

To use the OracleAS TopLink expression framework, define the finder in the OracleAS TopLink Mapping Workbench to specify the finder as a query object. Set the reference class to the name of the bean against which you run the query.

For more information, see "Query Objects".

If you build your finder in code, use the builder.getParameter() call to retrieve the arguments defined in the query. Use the arguments for comparison, combining them with various predicates and operators, such as equal(), like(), and anyOf().

Example 6-54 Using the OracleAS TopLink Expression Framework and Java Code

public static void addCustomerFinders(Descriptor descriptor) {
/* This code supports the query, Enumeration findCustomersInCity(String aCity)
Since this finder returns an Enumeration, it requires a ReadAllQuery. The finder 
is a "NAMED" finder that is registered with the QueryManager */
//1 Define the query.
ReadAllQuery query = new ReadAllQuery();
query.setName("findCustomersInCity");
query.addArgument("aCity");
query.setReferenceClass(CustomerBean.class);
//2 Use an expression
ExpressionBuilder builder = new ExpressionBuilder();
query.setSelectionCriteria
builder.get("city").like(builder.getParameter("aCity"));
/*3 You can set options on the query, such as  query.refreshIdentityMapResult(); 
*/
//4 Register the query with the querymanager.
descriptor.getQueryManager().addQuery("findCustomersInCity",query);
}

Generic Named Finder

You can use a named query without the need to provide the matching implementation on the Home interface. To do this, use the Generic Named finder provided by OracleAS TopLink. This finder takes the name of the named query and a vector of arguments as parameters.

Example 6-55 The Generic Named Finder

public Enumeration findAllByNamedQuery(String queryName, Vector arguments) 
throws RemoteException, FinderException;

For more information about finders, see "EJB Finders".

Redirect Queries

To perform complex operations, you can combine query redirectors with the OracleAS TopLink query framework. To create a redirector, implement the oracle.toplink.queryframework.QueryRedirector interface. The query mechanism executes the Object invokeQuery(DatabaseQuery query, DatabaseRow arguments, Session session) method and waits for the results.

OracleAS TopLink provides one pre-implemented redirector, the MethodBasedQueryRedirector method. To use this redirector, create a static invoke method on a class, and use the setMethodName(String) call to specify the method to invoke.

Example 6-56 Redirect Query

ReadObjectQuery query = new ReadObjectQuery(Employee.class);
query.setName("findEmployeeByAnEmployee");
query.addArgument("employee");

MethodBaseQueryRedirector redirector = new 
MethodBaseQueryRedirector(QueryRedirectorTest.class, 
"findEmployeeByAnEmployee");
query.setRedirector(redirector);
Descriptor descriptor = getSession().getDescriptor(query.getReferenceClass());
descriptor.getQueryManager().addQuery(query.getName(), query);

Vector arguments = new Vector();
arguments.addElement(employee);
objectFromDatabase = getSession().executeQuery(query,arguments);

public class QueryRedirectorTest{
public static Object findEmployeeByAnEmployee(DatabaseQuery query,    
oracle.toplink.publicinterface.DatabaseRow arguments, 
oracle.toplink.sessions.Session session) {
    ((ReadObjectQuery) query).setSelectionObject(arguments.get("employee"));
    return session.executeQuery(query);
}
}

EJBs and Redirect Finders

Redirect finders enable you to specify a finder in which the implementation is defined as a static method on an arbitrary helper class. When you invoke the finder, it redirects the call to the specified static method.

The finder can have any arbitrary parameters. If the finder includes parameters, OracleAS TopLink packages them into a vector and passes them to the redirect method.

Advantages

Because you define the redirect finder implementation independently from the bean that invokes it, you can build the redirect finder to accept any type and number of parameters. This enables you to create a generic redirect finder that accepts several different parameters and return types, depending on input parameters.

A common strategy for using redirect finders is to create a generic finder that:

The redirect method contains the logic required to extract the relevant data from the parameters and uses it to construct an OracleAS TopLink query.

Disadvantages

Redirect finders are complex and can be difficult to configure. They also require an extra helper method to define the query.

To create a redirect finder:
  1. Declare the finder in the ejb-jar.xml file, and leave the ejb-ql tag empty.

  2. Declare the finder on the Home interface, the localHome interface, or both, as required.

  3. Create an amendment method.

    For more information, see "Customizing OracleAS TopLink Descriptors with Amendment Methods".

  4. Start the OracleAS TopLink Mapping Workbench.

  5. Choose Advanced Properties > After Load from the menu for the bean.

  6. Specify the class and name of the static method to enable the amendment method for the descriptor.

The amendment method then adds a query to the descriptor's query manager, as follows:

ReadAllQuery query = new ReadAllQuery();
query.setRedirector(new MethodBaseQueryRedirector (examples.ejb.cmp20.advanced.
  FinderDefinitionHelper.class,"findAllEmployeesByStreetName"));
descriptor.getQueryManager().addQuery ("findAllEmployeesByStreetName", query);

The redirect method must return either a single entity bean (object) or a vector. Here are the possible method signatures:

public static Object redirectedQuery2(oracle.toplink.sessions.Sessions, Vector args)

and

public static Vector redirectedQuery4(oracle.toplink.sessions.Sessions, Vector args)

When you implement the query method, ensure that the method returns the correct type. For methods that return more than one bean, set the return type to java.util.Vector. OracleAS TopLink converts this result to java.util.Enumeration (or Collection) if required.


Note:

The redirect method also interprets an OracleAS TopLink session as a parameter. For more information about an OracleAS TopLink session, see Chapter 4, "Sessions".


At runtime, the client invokes the finder from the entity bean home and packages the arguments into the args vector in order of appearance from the finder method signature. The client passes the vector to the redirect finder, which uses them to execute an OracleAS TopLink expression.

Example 6-57 A Simple Redirect Query Implementation

public class RedirectorTest {
    private Session session;
    private Project project;
    public static void main(String args[]) {

       RedirectorTest test = new RedirectorTest();

       test.login();

            try {
            // Create the arguments to be used in the query
                Vector arguments = new Vector(1);
                arguments.add("Smith");

            // Run the query
                Object o = test.getSession()
                .executeQuery(test.redirectorExample(), arguments);
                o.toString();
            }
            catch (Exception e) {
                System.out.println("Exception caught -> " + e);
                e.printStackTrace();
            }
    }

    public ReadAllQuery redirectorExample() {

       // Create a redirector
       MethodBasedQueryRedirector redirector = new MethodBasedQueryRedirector();

       // Set the class containgin the public static method
       redirector.setMethodClass(RedirectorTest.class);

       // Set the name of the method to be run
       redirector.setMethodName("findEmployeeByLastName");

       // Create a query and add the redirector created above
       ReadAllQuery readAllQuery = new ReadAllQuery(Employee.class);
       readAllQuery.setRedirector(redirector);
       readAllQuery.addArgument("lastName");

       return readAllQuery;
    }
    //Call the static method
    public static Object findEmployeeByLastName(oracle.toplink.sessions
      .Session
    session, Vector arguments) {

       // Create a query
       ReadAllQuery raq = new ReadAllQuery();
       raq.setReferenceClass(Employee.class);
       raq.addArgument("lastName");

       // Create the selection criteria
       ExpressionBuilder employee = new ExpressionBuilder();
       Expression whereClause =
       employee.get("lastName").equal(arguments.firstElement());

       // Set the selection criteria
       raq.setSelectionCriteria(whereClause);

       return (Vector)session.executeQuery(raq, arguments);
    }
[...]
}

Queries Defined with the OracleAS TopLink Mapping Workbench

You can define several types of queries with the OracleAS TopLink Mapping Workbench, including custom SQL queries and named queries (which you can build using OracleAS TopLink expressions, EJB QL, or SQL).

For more information about the features and options available to create queries with the OracleAS TopLink Mapping Workbench, see "Understanding Descriptors," in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Query Managers

A query manager is a descriptor-owned object that controls descriptor access to the database. The query manager generates its own SQL to access the database in a transparent manner.

You can modify the query manager to do the following:

Customize the Default Query Methods

Query managers generate SQL for five database actions:

The OracleAS TopLink session class provides default query objects to perform these database functions. However, you can also use the query manager to provide custom query objects or SQL strings to perform these functions.

For example, to replace the OracleAS TopLink readObject function with a stored procedure call, specify the replacement code in the OracleAS TopLink Mapping Workbench. If you use a Sybase database, the stored procedure call to read an object looks like this:

EXEC PROC Read_Employee(@EMP_ID = 4653)

To implement this replacement code, add the following string to read the object:

EXEC PROC Read_Employee(@EMP_ID = #EMP_ID)

In the deployed project, the query manager substitutes the code you specified for the readObject call in any queries that include this call.

For more information about customizing default query methods in the OracleAS TopLink Mapping Workbench, see "Custom SQL Queries" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Customize the Default Query Methods in Java Code

To customize the query manager database access methods in Java code, use the getQueryManager() method to invoke the query manager. To change the default database access queries, use an amendment method listed in Table 6-5.

Table 6-5 Query Manager Methods for Database Access
To Change the Default Use This Query Manager Method

Delete call using a query

setDeleteQuery (DeleteObjectQuery query)

Delete call using SQL

setDeleteSQLString (String sqlString)

Insert call using a query

setInsertQuery (InsertObjectQuery query)

Insert call using SQL

setInsertSQLString (String sqlString)

ReadAll call using a query

setReadAllQuery (ReadAllQuery query)

ReadAll call using SQL

setReadAllSQLString (String sqlString)

ReadObject call using a query

setReadObjectQuery (ReadObjectQuery query)

ReadObject call using SQL

setReadObjectSQLString (String sqlString)

Update call using a query

setUpdateQuery (UpdateObjectQuery query)

Update call using SQL

setUpdateSQLString (String sqlString)


Note:

When you customize the update function for an application that uses optimistic locking, the custom update string must not write the object if the row version field has changed since the initial object was read. In addition, it must increment the version field if it writes the object successfully.

For example:

update Employee set F_NAME = #F_NAME, VERSION = VERSION + 1
where (EMP_ID = #EMP_ID) AND (VERSION = #VERSION)

The update string must also maintain the row count of the database.


Define Additional Join Expressions

You can set the query manager to automatically append an expression to every query it performs on a class. For example, you can add an expression that filters the database for the valid instances of a given class.

Use this to:

The query manager provides the setAdditionalJoinExpression() and the setMultipleTableJoinExpression() methods for this purpose.

Example 6-58 Registering a Query that Includes a Join Expression

/* The join expression in this example filters invalid instances of employee 
from the query */
public static void addToDescriptor(Descriptor descriptor)
{
    ExpressionBuilder builder = new ExpressionBuilder();
    descriptor.getQueryManager().setAdditionalJoinExpression((builder.getField("EMP.STATUS
      ").notEqual("DELETED")).and(builder.getField("EMP.STATUS").notEqual("HISTORICAL")));

}

Customize the Existence Check

When OracleAS TopLink writes an object to the database, OracleAS TopLink runs an existence check to determine whether to perform an insert or an update.

The query manager enables you to substitute custom logic for the existence check.

For more information on how to implement a custom existence check, see "Specifying Identity Mapping" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Use the following DescriptorQueryManager methods to modify the default existence checking:

checkCacheForDoesExist()
assumeExistenceForDoesExist()
assumeNonExistenceForDoesExist()
checkDatabaseForDoesExist()
setDoesExistQuery(DoesExistQuery)
setDoesExistSQLString(String)

Query Results

Queries can return different types of data, including:

Queries can also return EJBs in systems that use EJB finders.

For more information, see "EJB Finders".

Objects

OracleAS TopLink queries generally return Java objects as their result set. OracleAS TopLink queries can return

Collections

A collection is a group of Java objects related by a collection class that implements a Collection or Map interface. By default, ReadAll queries return results in a vector, but you can acquire the results in any collection class that implements the Collection or Map interface.

For more information on implementing Collection or Map interfaces, see the Oracle Application Server TopLink Mapping Workbench User's Guide.

Java Streams

A stream is a view of a collection, which can be a file, a device, or a Vector. A stream provides access to the collection, one element at a time in sequence. This makes it possible to implement stream classes in which the stream does not contain all the objects of a collection at the same time.

When a query is likely to generate a large result set, you can implement streams to improve performance.

For more information about streams, including advanced usage, see "Cursors and Streams".

Report Query Results

Report query provides developers with a way to access information or data from a set of objects and their related objects. Report query supports database reporting functions and features. Although the report query returns data (not objects), it does enable you to query the returned data and specify it at the object level.

For more information, see "ReportQuery".

Queries and the Cache

OracleAS TopLink caches objects written to and read from the database to maintain object identity. The sequence in which a query checks the cache and database affects query performance. By default, primary key queries check the cache before accessing the database, and all queries check the cache before rebuilding an object from its row.


Note:

You can override the default behavior in the caching policy configuration information in the OracleAS TopLink descriptor. For more information, see "Explicit Query Refreshes" .


This section illustrates ways to manipulate the query-cache relationship, including:

Cache Usage

OracleAS TopLink maintains a client-side cache to reduce the number of reads required from the database.

Cache and the Database

The cache in an OracleAS TopLink application holds objects that have already been read from or written to the database. Use of the cache in an OracleAS TopLink application reduces the number of accesses to the database. Because accessing the database is a time-intensive and resource-intensive act, an effective caching strategy is important to the efficiency of your application.

For more information about configuring and using the cache, see Chapter 8, "Cache".

In-Memory Query Cache Usage

In-memory querying enables you to perform queries on the cache rather than the database. In-memory querying supports the following relationships:

You can configure in-memory query cache usage at the query level for both readObject and readAll queries. OracleAS TopLink supports the following in-memory query features:

Table 6-6 identifies the in-memory queries options OracleAS TopLink supports.

Table 6-6 In-Memory Queries OracleAS TopLink Supports 
Type Query OracleAS TopLink Supports

Comparators

equal(..)
notEqual(..)
like(..) (with JDK 1.4 only)
lessThan(..)
lessThanOrEqual(..)
greaterThan(..)
greaterThanOrEqual(..)
between(...)
notBetween(...)
isNull()
notNull()
in(...)

Logical operators

or(..)
and(..)

Joining

get(..)
getAllowingNull(..)
anyOf(..)
anyOfAllowingNone(..)
Handling Exceptions Resulting from In-Memory Queries

In-memory queries fail for several reasons, the most common of which are:

OracleAS TopLink provides a mechanism to handle indirection exceptions. To specify how the application must handle these exceptions, use InMemoryQueryIndirectionPolicy class:

Conforming Results (UnitOfWork)

You can conform query results in the Unit of Work across one-to-many, as well as a combination of one-to-one and one-to-many relationships. The following is an example of a query across two levels of relationships, one-to-many and one-to-one.

Expression exp = 
  bldr.anyOf("managedEmployees").get("address").get("city").equal("Perth");


Note:

When relationships in an in-memory query use indirection, trigger all valueholders to ensure that the objects are available in the cache.


Exceptions thrown by the conform feature are masked by default. However, OracleAS TopLink includes an API that allows exceptions to be thrown rather than masked. The API is: uow.setShouldThrowConformExceptions(ARGUMENT).

ARGUMENT is an integer with one of the following values:

For more information, see "Validating a Unit of Work".

Cache and the Primary Key

When a query searches for a single object by primary key, OracleAS TopLink extracts the primary key from the query and attempts to return the object from the cache without accessing the database. If the object is not in the cache, the query executes against the database, builds the resulting object(s), and places it in the identity map.

If the query is based on a non-primary key selection criteria or is a readAll query, the query executes against the database (unless you have selected the checkCacheOnly() option). The query matches primary keys from the result set to objects in the cache and returns the cached objects, if any, in the result set.

If an object is not in the cache, OracleAS TopLink builds the object. If the query is a refreshing query, OracleAS TopLink updates the contents of any objects with the results from the query. Use Object identity (==) if you properly configure and use an identity map.

Clients can refresh objects when they want to ensure that they have the latest data at a particular time.

Disabling the Identity Map Cache Update During a Read Query

To disable the identity map cache update, which is normally performed by a read query, call the dontMaintainCache() method. This improves the query performance when you read objects that are not needed later by the application.

Example 6-59 Disabling the Identity Map Cache Update

This example demonstrates how code reads Employee objects from the database and writes the information to a flat file.

// Reads objects from the employee table and writes them to an employee file. 
void writeEmployeeTableToFile(String filename, Session session)
{
    Vector employeeObjects;
    ReadAllQuery query = new ReadAllQuery();
    query.setReferenceClass(Employee.class);
    query.setSelectionCriteria(new 
    ExpressionBuilder.get("id").greaterThan(100)); 
    query.dontMaintainCache();
    Vector employees = (Vector) session.executeQuery(query);
    // Write all the employee data to a file.
    Employee.writeToFile(filename, employees);
}

Refresh

You can refresh objects in the cache to ensure that they are current with the database while preserving object identity.

Object Refresh

To refresh objects in the cache with the data in the database, call the session.refreshObject() method or the readObjectQuery.setShouldRefreshIdentityMapResult(true) method.

Cascading Object Refresh

You can control the depth at which a refresh updates objects and their related objects. There are three options:

Refreshing the Identity Map Cache During a Read Query

Include the refreshIdentityMapResult() method in a query to force an identity map refresh with the results of the query.

Example 6-60 Refreshing the Result of a Query in the Identity Map Cache During a Read Query

ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(Employee.class);
query.setSelectionCriteria(new 
ExpressionBuilder().get("lastName").equal("Smith")); 
query.refreshIdentityMapResult();
Employee employee = (Employee) session.executeQuery(query);

The refreshIdentityMapResult() method refreshes the object's attributes, but not the attributes of its privately owned parts. However, under most circumstances, refresh an object's privately owned parts and other related objects to ensure consistency with the database.

To refresh privately owned or related parts, use the following methods:

Example 6-61 Using the cascadePrivateParts Method

ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.refreshIdentityMapResult();
query.cascadePrivateParts();
Vector employees = (Vector) session.executeQuery(query);


Note:

If the object is in the session cache, you can also use the refreshObject() method to refresh an object and its privately owned parts.


Caching Query Results

When an application executes a query, you can store the results of that query in the cache. This is useful for frequently executed queries that run against static data. Caching the results also ensures that the query returns the same results for a given period of time (for example: within the scope of a particular transaction) and then refreshes the data later if required.

Query Objects and Write Operations

Although OracleAS TopLink applications most often perform database write operations through a Unit of Work, you can also write to the database with query objects. This section describes some of the more common strategies for using write queries and includes discussions on:

Write Query Overview

To execute a write query, use a WriteObjectQuery instance instead of using the writeObject() method of the session. Likewise, substitute DeleteObjectQuery, UpdateObjectQuery and InsertObjectQuery objects for their respective Session methods.

Example 6-62 Using a WriteObjectQuery Object

WriteObjectQuery writeQuery = new WriteObjectQuery();
writeQuery.setObject(domainObject);
session.executeQuery(writeQuery);

Example 6-63 Using Other Write Query Objects with Similar Syntax

InsertObjectQuery insertQuery= new InsertObjectQuery();
insertQuery.setObject(domainObject);
session.executeQuery(insertQuery);

/* When you use UpdateObjectQuery without a Unit of Work, UpdateObjectQuery 
writes all direct attributes to the database */
UpdateObjectQuery updateQuery= new UpdateObjectQuery();
updateQuery.setObject(domainObject2);
session.executeQuery(updateQuery);

DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
deleteQuery.setObject(domainObject2);
session.executeQuery(deleteQuery);

Non-Cascading Write Queries

When you execute a write query, it writes both the object and its privately owned parts to the database by default. To build write queries that do not update privately owned parts, include the dontCascadeParts() method in your query definition.

Use this method to:

Example 6-64 Performing a Non-Cascading Write Query

// theEmployee is an existing employee read from the database.
Employee.setFirstName("Bob");
UpdateObjectQuery query = new UpdateObjectQuery();
query.setObject(Employee);
query.dontCascadeParts();
session.executeQuery(query);

Disabling the Identity Map Cache During a Write Query

When you write objects to the database, OracleAS TopLink copies them to the session cache by default. To disable this behavior within a query, call the dontMaintainCache() method within the query. This improves query performance when you insert objects into the database, but must only be used on objects that will not be required later by the application.

Example 6-65 Disabling the Identity Map Cache During a Write Query

This code reads all the objects from a flat file and writes new copies of the objects into a table.

// Reads objects from an employee file and writes them to the employee table.
void createEmployeeTable(String filename, Session session)
{
   Iterator iterator;
   Employee employee;
   // Read the employee data file.
   List employees = Employee.parseFromFile(filename);
   Iterator iterator = employees.iterator();
   while (iterator.hasNext()) {
      Employee employee = (Employee) iterator.next();
      InsertObjectQuery query = new InsertObjectQuery();
      query.setObject(employee);
      query.dontMaintainCache();
      session.executeQuery(query);
}


Caution:

Disable the identity map only when object identity is unimportant in subsequent operations.


Using Query Objects to Customize the Default Database Operations

OracleAS TopLink provides default querying behavior for each of the read and write operations that is sufficient for most applications. In addition, applications can define their own custom queries where required:

Query Object Performance Options

Several optimizations are available that improve the performance of your queries, including:

For more information about improving the performance of your application and information on how to optimize queries, see Chapter 10, "Tuning for Performance".

Batch Reading

Batch reading propagates query selection criteria through an object's relationship attribute mappings. You can also nest batch reads down through complex object graphs. This significantly reduces the number of required SQL select statements and improves database access efficiency.

For example, in reading n employees and their related projects, OracleAS TopLink may require n + 1 selects. All employees are read at once, but the projects of each are read individually. With batch reading all related projects can also be read with one select by using the original selection criteria, for a total of only 2 selects.

To implement batch reading, use one of the following methods:

You can combine batch reading and indirection to provide controlled reading of object attributes. For example, if you have one-to-one backpointer relationship attributes, you can defer backpointer instantiation until the end of the query, when all parent and owning objects are instantiated. This prevents unnecessary database access and optimizes OracleAS TopLink cache use.

Guidelines for Implementing Batch Reading

Consider the following guidelines when you implement batch reading:

For more information, see "Reading Case 2: Batch Reading Objects".

Join Reading

When OracleAS TopLink queries, it can use joins to check values from other objects or other tables that represent parts of the same object. Although this works well under most circumstances, it can cause problems when you query against a one-to-one relationship in which one side of the relationship is not present.

For example, Employee objects may have an Address object, but if the Address is unknown, it is null at the object level and has a null foreign key at the database level. When you attempt a read that traverses the relationship, missing objects cause the query to return unexpected results. Consider the expression:

(emp.get("firstName").equal("Steve")).or(emp.get("address"). get("city").equal("Ottawa"))

In this case, employees with no address do not appear in the result set, regardless of their first name. Although not obvious at the object level, this behavior is fundamental to the nature of relational databases.

Outer joins rectify this problem in the databases that support them. In this example, the use of an outer join provides the expected result: all employees named Steve appear in the result set, even if their address is unknown.

To implement an outer join, use getAllowingNull() rather than get(), and anyOfAllowingNone() rather than anyOf().

For example:

(emp.get("firstName").equal("Steve")).or
  (emp.getAllowingNull ("address").get("city").equal("Ottawa"))

Support and syntax for outer joins vary widely between databases and database drivers. OracleAS TopLink supports outer joins for Oracle databases, IBM DB2, SQL Anywhere, Microsoft Access, Microsoft SQL Server, Sybase SQL Server, and the JDBC outer join syntax. Of these, only Oracle supports the outer join semantics in or clauses.

You can also use outer joins with ordering.

For more information, see "Ordering for Read All Queries" .

Join reading enables you to read data from a one-to-one mapping in conjunction with data from the original query. Join reading is available only for one-to-one mappings. To implement join reading, use either of the following methods:

For more information about joins as a performance tool, see Chapter 10, "Tuning for Performance".

ReportQuery

Report query enables you to retrieve data from a set of objects and their related objects. Report query supports database reporting functions and features. Although the report query returns data rather than objects, it still enables you to query and specify the data at the object level.

The ReportQuery API returns a collection of ReportQueryResult objects, similar in structure and behavior to a DatabaseRow or a Map.

Report query allows you to:

Example 6-66 Querying Reporting Information on Employees

This example reports the total and average salaries for Canadian employees grouped by their city.

ExpressionBuilder emp = new ExpressionBuilder();
ReportQuery query = new ReportQuery(emp);
query.setReferenceClass(Employee.class);
query.addMaximum("max-salary", emp.get("salary"));
query.addAverage("average-salary", emp.get("salary"));
query.addAttribute("city", emp.get("address").get("city"));

query.setSelectionCriteria(emp.get("address").get("country").equal("Canada"));
query.addOrdering(emp.get("address").get("city"));
query.addGrouping(emp.get("address").get("city"));
Vector reports = (Vector) session.executeQuery(query);

Table 6-7 summarizes the most common public methods for ReportQuery. For more information about the available methods for the ReportQuery, see the Oracle Application Server TopLink API Reference.

Table 6-7 Elements for Report Query  
Element Default Method Name

Adding items to select

Nothing selected

addAttribute(String itemName)
addAttribute(String itemName, Expression 
attributeExpression)
addAverage(String itemName)
addAverage(String itemName, Expression 
attributeExpression)
addMaximum(String itemName)
addMaximum(String itemName, Expression 
attributeExpression)
addMinimum(String itemName)
addMinimum(String itemName, Expression 
attributeExpression)
addSum(String itemName)
addSum(String itemName, Expression 
attributeExpression)
addStandardDeviation(String itemName)
addStandardDeviation(String itemName, Expression 
attributeExpression)
addVariance(String itemName)
addVariance(String itemName, Expression 
attributeExpression)
addCount()
addCount(String itemName)
addCount(String itemName, Expression 
attributeExpression)
addItem(String itemName, Expression 
attributeExpression)
addFunctionItem(String itemName, Expression 
attributeExpression, String functionName)

Group by

Not grouped

addGrouping(String attributeName)
addGrouping(Expression expression)

Retrieving primary keys

Not retrieved

retrievePrimaryKeys()
dontRetrievePrimaryKeys()
setShouldRetrievePrimaryKeys(boolean 
shouldRetrievePrimaryKeys)


Note:

Because ReportQuery inherits from ReadAllQuery, it also supports most ReadAllQuery properties.


Partial Attribute Reading

You can query for parts of objects rather than complete objects. For example, you can build a read query that returns a subset of an object's attributes rather than the entire object. This improves database read performance when you do not require the complete object.

To configure partial object reading, use the addPartialAttribute() method. For more information, see "Partial Object Reading".

Consider the following when you use partial object reading:

Cache Results In Query Objects

Query objects maintain an internal cache of the objects previously returned by the query. This improves query performance and ensures that the query always returns the same objects.

The internal cache is disabled by default. To enable it, use the cacheQueryResults() method in the query.

Example 6-67 Using the Internal Query Object Cache

ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(Employee.class);
query.cacheQueryResults();

// The query object reads from the database the first time you invoke it.
Employee employee = (Employee) session.executeQuery(query);

/* On this second call to execute the query, the query object does not read from 
the database, but reads from the query object's internal cache instead */
Employee employee = (Employee) session.executeQuery(query);

Oracle Extension Support

OracleAS TopLink supports the following Oracle enterprise enhancements for Oracle databases:

Oracle Hints and the OracleAS TopLink Query Framework

Oracle Hints is an Oracle database feature through which a developer makes decisions usually reserved for the optimizer. Developers use hints to specify things such as join order for a join statement, or the optimization approach of a SQL call.

The OracleAS TopLink query framework supports Oracle Hints with the following API:

addHintString("/*[hints or comments]*/");

OracleAS TopLink adds the hint to the SQL string as a comment immediately following a SELECT, UPDATE, INSERT, or DELETE statement.

To add hints to a read query:

  1. Create a ReadObjectQuery or a ReadAllQuery

  2. Set the selection criteria.

  3. Add hints as needed.

For example, the following code uses the FULL hint (which explicitly chooses a full table scan for the specified table):

// This line sets up the query
ReadObjectQuery query = new ReadObjectQuery(Employee.class);
query.setSelectionCritera(new ExpressionBuilder().get("id").equal(new Integer(1));
// This line adds the hint
query.addHintString("/*+ FULL */" ); 

This code generates the following SQL:

SELECT /*+ FULL */ FROM EMPLOYEE WHERE ID=1

To add hints to WRITE, INSERT, UPDATE, and DELETE, create custom queries for these operations in the OracleAS TopLink query framework, then specify hints as required.

For more information about the available hints, see the Oracle database documentation.

Hierarchical Queries

Hierarchical Queries is an Oracle database mechanism that enables you to select database rows based on hierarchical order. For example, you can design a query that reads the row of a given employee, followed by the rows of people the employee manages, followed by their managed employees, and so on.

To create a hierarchical query, use the setHierarchicalQueryClause() method. This method takes three parameters, as follows:

setHierarchicalQueryClause(StartWith, ConnectBy, OrderSibling)

This expression requires all three parameters, as follows:

StartWith Parameter

The StartWith parameter in the expression specifies the first object in the hierarchy. This parameter mirrors the Oracle database START WITH clause.

To include a StartWith parameter, build an expression to specify the appropriate object, and pass it as a parameter in the setHierarchicalQueryClause() method. If you do not specify the root object for the hierarchy, set this value to NULL.

ConnectBy Parameter

The ConnectBy parameter specifies the relationship that creates the hierarchy. This parameter mirrors the Oracle database CONNECT BY clause.

Build an expression to specify the ConnectBy parameter, and pass it as a parameter in the setHierarchicalQueryClause() method. Because this parameter defines the nature of the hierarchy, it is required for the setHierarchicalQueryClause() implementation.

OrderSibling Parameter

The OrderSibling parameter in the expression specifies the order in which the query returns sibling objects in the hierarchy. This parameter mirrors the Oracle database ORDER SIBLINGS clause.

To include an OrderSibling parameter, define a vector, and to include the order criteria, use the addElement() call. Pass the vector as the third parameter in the setHierarchicalQueryClause() method. If you do not specify an order, set this value to NULL.

Example 6-68 Hierarchical Query

ReadAllQuery raq = new ReadAllQuery(Employee.class);
// Specify a START WITH expression
Expression startExpr = expressionBuilder.get("id").equal(new Integer(1));
// Specifies a CONNECT BY expression
Expression connectBy = expressionBuilder.get("managedEmployees");
//Specifies an ORDER SIBLINGS BY vector
Vector order = new Vector();
order.addElement(expressionBuilder.get("lastName"));
order.addElement(expressionBuilder.get("firstName"));
raq.setHierarchicalQueryClause(startExpr, connectBy, order);
Vector employees = uow.executeQuery(raq);

This code generates the following SQL:

SELECT * FROM EMPLOYEE START WITH ID=1 CONNECT BY PRIOR ID=MANAGER_ID ORDER 
SIBLINGS BY LAST_NAME, FIRST_NAME

Advanced Querying

OracleAS TopLink offers several advanced mechanisms and techniques that enhance your queries. This section describes the following:

Creating Additional Query Keys

A query key is an alias for a field name. Instead of referring to a field using a DBMS-specific field name such as F_NAME, query keys allow OracleAS TopLink expressions to refer to the field using Java attribute names, such as firstName.

For more information about Query Keys, see "Query Keys".

You can implement query keys either with the OracleAS TopLink Mapping Workbench or in Java.

For more information about implementing query keys with the OracleAS TopLink Mapping Workbench, see "Working with Query Keys" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Implementing Query Keys in Java

To add and register query keys with a descriptor, implement the following methods:

Example 6-69 Implementing a One-to-One Query Key

// Add a query key for the foreign key field using the direct method
descriptor.addDirectQueryKey("managerId", "MANAGER_ID");

// The same query key can also be added through the add method
DirectQueryKey directQueryKey = new DirectQueryKey();
directQueryKey.setName("managerId");
directQueryKey.setFieldName("MANAGER_ID");
descriptor.addQueryKey(directQueryKey);

/* Add a one-to-one query key for the large project that the employee is a 
leader of (this assumes only one project) */
OneToOneQueryKey projectQueryKey = new OneToOneQueryKey();
projectQueryKey.setName("managedLargeProject");
projectQueryKey.setReferenceClass(LargeProject.class);
ExpressionBuilder builder = new ExpressionBuilder();
projectQueryKey.setJoinCriteria(builder.getField("PROJECT.LEADER_
ID").equal(builder.getParameter("EMPLOYEE.EMP_ID")));
descriptor.addQueryKey(projectQueryKey);

Example 6-70 Implementing a One-to-Many Query Key

/* Implements keys for the projects where the employee manages multiple projects 
*/
OneToManyQueryKey projectsQueryKey = new OneToManyQueryKey();
projectsQueryKey.setName("managedProjects");
projectsQueryKey.setReferenceClass(Project.class);
ExpressionBuilder builder = new ExpressionBuilder();
projectsQueryKey.setJoinCriteria(builder.getField("PROJECT.LEADER_
ID").equal(builder.getParameter("EMPLOYEE.EMP_ID")));
descriptor.addQueryKey(projectsQueryKey);
// Next define the mappings. 
...

Example 6-71 Implementing a Many-to-Many Query Key

ManyToManyQueryKey key = new ManyToManyQueryKey();
key.setName("myAs");
key.setReferenceClass(A.class);
ExpressionBuilder builder = new ExpressionBuilder();
Expression exp = builder.getField("AB_JOIN.B_
ID").equal(builder.getParameter("B.ID" ));
Expression exp1 = builder.getField("AB_JOIN.A_
ID").equal(builder.getField("A.ID") );
key.setJoinCriteria(exp.and(exp1));
descriptor.addQueryKey(key);

Querying on Interfaces

When you define descriptors for an interface to enable querying, OracleAS TopLink supports querying on an interface, as follows:

Querying on an Inheritance Hierarchy

When you query on a class that is part of an inheritance hierarchy, the session checks the descriptor to determine the type of the class:

Cursors and Streams

Cursors and streams are related mechanisms that enable you to work with large result sets efficiently.

Cursors and Java Iterators

The OracleAS TopLink scrollable cursor enables you to scroll through a result set from the database without reading the whole result set in a single database read. The ScrollableCursor class implements the Java ListIterator interface to allow for direct and relative access within the stream. Scrollable cursors also enable you to scroll forward and backward through the stream.

Traversing Data with Scrollable Cursors

Several methods enable you to navigate data with a scrollable cursor:

Several strategies are available for traversing data with cursors. For example, to start at the end of the data set and work toward the first record:

  1. Call the afterLast() method to place the cursor after the last row in the result set.

  2. Use the hasPrevious() method to determine whether there is a record above the current record. This method returns FALSE when you reach the final record in the data set.

  3. If the hasPrevious() method returns TRUE, call the previous() method to move the cursor to the row above the current row and read that object.

These are common methods for data traversal, but they are not the only available methods. For more information about the available methods, see the Oracle Application Server TopLink API Reference.

To use the ScrollableCursor object, the JDBC driver must be compatible with JDBC 2.0 specifications.

Example 6-72 Traversing with a Scrollable Cursor

ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.useScrollableCursor();
ScrollableCursor cursor = (ScrollableCursor) session.executeQuery(query);

while (cursor.hasNext()) {
    System.out.println(cursor.next().toString());
}
cursor.close();

Java Streams

Java streams enable you to retrieve query results as individual records or groups of records, which can result in a performance increase. You can use streams to build efficient OracleAS TopLink queries, especially when the queries are likely to generate large result sets.

Cursored Stream Support

Cursored streams combine the iterative ability of the ScrollableCursor interface with OracleAS TopLink support for streams. The result is the ability to read back a query result set from the database in manageable subsets, and to scroll through the result set stream.

The useCursoredStream() method of the ReadAllQuery class provides cursored stream support.

Example 6-73 Cursored Streams

CursoredStream stream;
ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.useCursoredStream();
stream = (CursoredStream) session.executeQuery(query);

The query returns an instance of CursoredStream rather than a Vector, which can be a more efficient approach. For example, consider the following two code examples. Example 6-74 returns a Vector that contains all employee objects. If ACME has 10,000 employees, the Vector contains references to 10,000 Employee objects.

Example 6-74 Using a Vector

ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
Enumeration employeeEnumeration;

Vector employees = (Vector) session.executeQuery(query);
employeeEnumeration = employee.elements();

while (employeeEnumeration.hasMoreElements())
{
Employee employee = (Employee) employeeEnumeration.nextElement();
employee.doSomeWork();
}

Example  returns a CursoredStream instance rather than a Vector. The CursoredStream collection appears to contain all 10,000 objects, but initially contains a reference only to the first 10 Employee objects. It retrieves the remaining objects in the collection as they are needed. In many cases, the application never needs to read all the objects.

The following approach results in a significant performance increase:

ReadAllQuery query = new ReadAllQuery();
query.setReferenceClass(Employee.class);
query.useCursoredStream();

CursoredStream stream = (CursoredStream) session.executeQuery(query);
while (! stream.atEnd()) 
{
    Employee employee = (Employee) stream.read();
    employee.doSomeWork();
    stream.releasePrevious();
}
stream.close();


Note:

The releasePrevious() message is optional. This releases any previously read objects and frees system memory. Even though released objects are removed from the cursored stream storage, they remain in the identity map.


Optimizing Streams

To optimize CursoredStream performance, provide a threshold and page size to the useCursoredStream(Threshold, PageSize) method, as follows:

When you execute a batch-type operation, use the dontMaintainCache() option with a cursored stream. A batch operation performs simple operations on large numbers of objects and then discards the objects. Cursored streams create the required objects only as needed, and the dontMaintainCache() option ensures that these transient objects are not cached.

Querying Across Variable One-to-One Mappings

OracleAS TopLink does not provide a method to directly query against variable one-to-one mappings. To query against this type of mapping, combine OracleAS TopLink DirectQueryKeys and OracleAS TopLink ReportQueries to create query selection criteria for classes that implement the interface, as follows:

  1. Create two DirectQueryKeys to query for the possible implementors of the interface.

    • The first DirectQueryKey is for the class indicator field for the variable one-to-one mapping.

    • The second DirectQueryKey is for the foreign key to the class or table that implements the interface.

  2. Create a subSelect statement for each concrete class that implements the interface included in the query selection criteria.

  3. Implement a ReportQuery.

Example 6-75 Creating DirectQueryKeys

/*The DirectQueryKeys as generated in the OracleAS TopLink project java source 
code from the OracleAS TopLink Mapping Workbench */
...
descriptor.addDirectQueryKey("locationTypeCode","DEALLOCATION.DEALLOCATIONOBJECTTYPE");
descriptor.addDirectQueryKey("locationTypeId","DEALLOCATION.DEALLOCATIONOBJECTID");	

EJB Finders

The OracleAS TopLink query framework enables you to construct finders, which are queries that retrieve entity beans. This section describes OracleAS TopLink support for finders, and includes discussions on the following topics and techniques:

Defining Finders in OracleAS TopLink

To define a finder method for an entity bean that uses the OracleAS TopLink query framework, follow these steps:

  1. Declare the finder in the ejb-jar.xml file.

  2. Define the finder method.

    • For EJB 1.1 beans, define the method on the entity bean's remote interface.

    • For EJB 2.0 beans, define the method on the entity bean's remoteHome or localHome interface.

  3. Use the OracleAS TopLink Mapping Workbench to change any options on finders.

  4. If required, create an implementation for the query. Some query options require a query definition in code on a helper class, but most common queries do not.

When you use OracleAS TopLink CMP, define finder methods on the bean's Home interface, not in the entity bean itself. OracleAS TopLink CMP provides this functionality and offers several strategies to create and customize finders. The EJB container and OracleAS TopLink automatically generate the implementation.

ejb-jar.xml Finder Options

The ejb-jar.xml file contains a project's EJB entity bean information, including definitions for any finders used for the beans. To create and maintain the ejb-jar.xml file, use either a text editor or the OracleAS TopLink Sessions Editor.

entity tag

The entity tag encapsulates a definition for an EJB entity bean. Each bean has its own entity tag that contains several other tags that define bean functionality, including bean finders.

Example 6-76 illustrates the structure of a typical finder defined within the ejb-jar.xml file.

Example 6-76 A Simple Finder Within the ejb-jar.xml File

<entity>...
    <query>
        <query-method>
            <method-name>findLargeAccounts</method-name>
                <method-params>
                <method-param>double</method-param>
            </method-params>
        </query-method>
    <ejb-ql><![CDATA[SELECT OBJECT(account) FROM AccountBean account WHERE
      account.balance > ?1]]></ejb-ql>     </query> ... </entity>
query Section

The entity tag contains zero or more query elements. Each query tag corresponds to a finder method defined on the bean's home or local Home interface.


Note:

You can share a single query between both Home interfaces, as follows:

  • Define the same finder (same name, return type, and parameters) on both Home interfaces.

  • Include a single query element in the ejb-jar.xml file.


Here are the elements defined in the query section of the ejb-jar.xml file:

Call Finders

Call finders enable you to create queries dynamically and generate the queries at runtime rather than deployment time. Call finders pass an OracleAS TopLink SQLCall or StoredProcedureCall as a parameter and return an Enumeration.

Creating Call Finders

OracleAS TopLink provides the implementation for Call finders. To use this feature in a bean, add the following finder definition to the Home interface of your bean.

public Enumeration findAll(Call call) throws RemoteException, FinderException;

Executing a Call Finder

When you execute a Call finder, OracleAS TopLink creates the call on the client using the OracleAS TopLink interface oracle.toplink.queryframework.Call. This call has three implementors: EJBQLCall, SQLCall and StoredProcedureCall.

Example 6-77 Executing a Call Finder (Select Statement)

{
    SQLCall call = new SQLCall();
    call.setSQLString("SELECT * FROM EMPLOYEE");
    Enumeration employees = getEmployeeHome().findAll(call);
}

Example 6-78 Executing a Call Finder (Stored Procedure)

{
    StoredProcedureCall call = new StoredProcedureCall();
    call.setProcedureName("READ_ALL_EMPLOYEES");
    Enumeration employees = getEmployeeHome().findAll(call);
}

Expression Finders

To define finder query logic, use OracleAS TopLink expressions. Expression finders support dynamic queries that you generate at runtime rather than deployment time. To use an expression finder, pass the expression as a parameter to a finder that returns an Enumeration.

Example 6-79 Executing an Expression Finder

{
    Expression expression = new 
    ExpressionBuilder().get("firstName").like("J%");
    Enumeration employees = 
    getEmployeeHome().findAll(expression);
}

EJB QL Finders

EJB QL is the standard query language defined in the EJB 2.0 specification. OracleAS TopLink supports EJB QL for both EJB 1.1 and EJB 2.0 beans. EJB QL finders enable you to specify an EJB QL string as the implementation of the query.

EJB QL offers several advantages:

The disadvantage of EJB QL is that it is difficult to use when you construct complex queries.

To create an EJB QL finder under EJB 1.1:
  1. Declare the finder on the remote interface.

  2. Start the OracleAS TopLink Mapping Workbench.

  3. Go to the Queries > Finders > Named Queries tab for the bean.

  4. Add a finder and give it a name that matches the method name you declared in Step 1.

  5. Set the required parameters.

  6. Set Query Format to EJB QL, and enter the EJB QL query in the Query String field.

To create an EJB QL finder under EJB 2.0:
  1. Declare the finder on either the localHome or the remoteHome interface.

  2. Start the OracleAS TopLink Mapping Workbench.

  3. Re-import the ejb-jar.xml file to synchronize the project to the file.

    The OracleAS TopLink Mapping Workbench synchronizes changes between the project and the ejb-jar.xml file.

The following is an example of a simple EJB QL query that requires one parameter. In this example, the question mark ("?") in?1 specifies a parameter.

SELECT OBJECT(employee) FROM Employee employee WHERE (employee.name =?1)

To create an EJB QL finder for a CMP bean:
  1. Declare the finder in the ejb-jar.xml file, and enter the EJB QL string in the ejb-ql tag.

  2. Declare the finder on the Home interface, the localHome interface, or both, as required.

  3. Start the OracleAS TopLink Mapping Workbench.

  4. Specify the ejb-jar.xml file location and choose File > Updated Project from the ejb-jar.xml file to read in the finders.

  5. Go to the Queries > Finders > Named Queries tab for the bean.

  6. Add a finder, and give it the same name as the finder you declared on your bean's home. Then add any required parameters.

  7. Select and configure the finder.

The following is an example of a simple EJB QL query that requires one parameter. In this example, the question mark ("?") in?1 specifies a parameter.

SELECT OBJECT(employee) FROM Employee employee WHERE (employee.name =?1)

ReadAll Query and EJB QL

To execute a query normally, you supply either a reference class or a SELECT clause.

The basic API for a ReadAll query with EJB QL is:

ReadAllQuery setEJBQLString("...")

Example 6-80 ReadAllQuery Using EJB QL

ReadAllQuery theQuery = new ReadAllQuery();
theQuery.setReferenceClass(EmployeeBean.class);
theQuery.setEJBQLString("SELECT OBJECT(emp) FROM EmployeeBean emp");
...
Vector returnedObjects = (Vector)aSession.executeQuery(theQuery);

Example 6-81 ReadAllQuery Using EJB QL and Passing Arguments

This code creates, populates, and passes a vector of arguments into the executeQuery method

// First define the query
ReadAllQuery theQuery = new ReadAllQuery();
theQuery.setReferenceClass(EmployeeBean.class);
theQuery.setEJBQLString("SELECT OBJECT(emp) FROM EmployeeBean emp WHERE 
emp.firstName = ?1");
theQuery.addArgument("1");
...
// Next define the Arguments
Vector theArguments = new Vector();
theArguments.add("Bob");
...
// Finally execute the query passing in the arguments
Vector returnedObjects = (Vector)aSession.executeQuery(theQuery, theArguments);

EJB QL Session Queries

When you execute EJB QL directly against the session, it returns a vector of the objects specified by the reference class. The basic API is as follows:

aSession.readAllObjects(<ReferenceClass>, <EJBQLCall>)

Example 6-82 EJB QL Session Query

/* <EJBQLCall> is the EJBQL string to be executed and <ReferenceClass> is the 
return class type */
// Call ReadAllObjects on a session.
Vector theObjects = (Vector)aSession.readAllObjects(EmployeeBean.class, new 
EJBQLCall( "SELECT OBJECT (emp) from EmployeeBean emp));

SQL Finders

You can use custom SQL code to specify finder logic. SQL enables you to implement logic that might not be possible to express with OracleAS TopLink expressions or EJB QL.

To create a SQL finder:
  1. Declare the finder in the ejb-jar.xml file, and leave the ejb-ql tag empty.

  2. Start the OracleAS TopLink Mapping Workbench.

  3. Specify the ejb-jar.xml file location and choose File > Updated Project from the ejb-jar.xml file to read in the finders.

  4. Go the Queries > Named Queries tab for the bean.

  5. Select the finder, check the SQL radio button, and enter the SQL string.

  6. Configure the finder.

The following is an example of a simple SQL finder that requires one parameter. In this example, the hash character, '#', is used to bind the argument projectName within the SQL string.

SELECT * FROM EJB_PROJECT WHERE (PROJ_NAME = #projectName)

Dynamic Finders

OracleAS TopLink provides several predefined finders you can use to execute dynamic queries, in which the logic is determined by the user at runtime. The OracleAS TopLink runtime reserves the names for these finders; they cannot be reused for other finders.

The predefined finders are:

EJBObject findOneByEJBQL(String ejbql, Vector args)
Collection findManyByEJBQL(String ejbql, Vector args)
EJBObject findOneBySQL(String sql, Vector args)
Collection findManyBySQL(String sql, Vector args)
EJBObject findOneByQuery(DatabaseQuery query, Vector args)
Collection findManyByQuery(DatabaseQuery query, Vector args)


Note:

With EJB 2.0, if the finder is located on a local home, replace EJBObject with EJBLocalObject in finders that contain findOneby.


You can also use each of these finders without a vector of arguments. For example, EJBObject findOneByEJBQL(String ejbql) is a valid dynamic finder, but you must replace the return type of EJBObject with your bean's component interface.

To create a dynamic finder:
  1. Declare the finder in the ejb-jar.xml file, and leave the ejb-ql tag empty.

  2. Declare the finder on the Home interface, the localHome interface, or both, as required.

  3. Start the OracleAS TopLink Mapping Workbench.

  4. Specify the ejb-jar.xml file location and choose File > Updated Project from the ejb-jar.xml file to read in the finders.

  5. Go to the Queries > Named Queries tab for the bean.

  6. Select and configure the finder.


    Notes:

    If the advanced query options in "Advanced Finder Options" are not required, you need only complete steps 1 and 2.

    Do not configure any query options for the findOneByQuery and findManyByQuery dynamic finders, because the client creates the query at runtime and passes it as a parameter to the finder. Set any required system options on that query.


Using findAll

In addition to the preceding dynamic finder, OracleAS TopLink provides a default findAll query that returns all the beans of a given type. As with other dynamic finders, the OracleAS TopLink runtime reserves the name findAll.

For more information about defining and configuring the finder, see "To create a dynamic finder:".

Using findByPrimaryKey

OracleAS TopLink creates the findByPrimaryKey finder to a bean class when the class initializes. You can configure the findByPrimaryKey finder with the various OracleAS TopLink query options.

Because the EJB 2.0 specification requires the container to implement the findByPrimaryKey call on each bean Home interface, do not delete this finder from a bean.

ReadAll Finders

ReadAll finders enable you to create dynamic queries that you generate at runtime rather than deployment time. To use a ReadAll finder, pass an OracleAS TopLink ReadAllQuery as a parameter to a finder that returns an Enumeration.

Creating READALL Finders

OracleAS TopLink provides an implementation for ReadAll finders. To use this feature in a bean, add the following finder definition to the Home interface of your bean.

public Enumeration findAll(ReadAllQuery query) throws RemoteException, 
FinderException;

To execute a ReadAll finder, create the query on the client.

Example 6-83 A ReadAll Finder

{
    ReadAllQuery query = new ReadAllQuery(Employee.class);
    query.addJoinedAttribute("address");
    Enumeration employees = getEmployeeHome().findAll(query);
}

Choosing the Best Finder Type for Your Query

To optimize performance, choose the finder type that best suits your needs.

Using the OracleAS TopLink Expression Framework

Using OracleAS TopLink expressions offers the following advantages:

Because expressions enable you to specify finder search criteria based on the object model, they are frequently the best choice for constructing your finders.

For more information about implementing finders using OracleAS TopLink expressions, see "Expression Finders".

Using Redirect Finders

Redirect finders enable you to implement a finder that is defined on an arbitrary helper class as a static method. When you invoke the finder, OracleAS TopLink redirects the call to the specified static method.

Redirect queries are complex and require an extra helper method to define the query. However, because they support complex logic, they are often the best choice when you need to implement logic unrelated to the bean on which the redirect method is called.

Using SQL

Using SQL to define a finder offers the following advantages:

SQL finders also have the following disadvantages:

ejbSelect

The ejbSelect method is a query method intended for internal use within an entity bean instance. Specified on the abstract bean itself, the ejbSelect method is not directly exposed to the client in the home or component interface. Defined as abstract, each bean can include zero or more such methods.

Select methods have the following characteristics:

The format for an ejbSelect method definition looks like this:

public abstract type ejbSelect<METHOD>(...);

The ejbSelect query return type is not restricted to the entity bean type on which the ejbSelect is invoked. Instead, it can return any type corresponding to a container-managed relationship or container-managed field.

Although the select method is not based on the identity of the entity bean instance on which it is invoked, it can use the primary key of an entity bean as an argument. This creates a query that is logically scoped to a particular entity bean instance.

To create an ejbSelect:
  1. Declare the ejbSelect in the ejb-jar.xml file, enter the EJB QL string in the ejb-ql tag, and specify the return type in the result-type-mapping tag (if required).

  2. Declare the ejbSelect on the abstract bean class.

  3. Start the OracleAS TopLink Mapping Workbench.

  4. Specify the ejb-jar.xml file location, and choose File > Updated Project from the ejb-jar.xml file to read in the finders.

  5. Go the Queries > Named Queries tab for the bean.

  6. Select and configure the ejbSelect query.

Advanced Finder Options

The default finder configuration is appropriate for most applications. However, finders also allow for several advanced configuration options.

Caching Options

You can apply various configurations to the underlying query to achieve the correct caching behavior for the application. There are several ways to control the caching options for queries. For most queries, you can set caching options with the OracleAS TopLink Mapping Workbench. For more information, see "Caching objects" in the Oracle Application Server TopLink Mapping Workbench User's Guide.

You can set the caching options on a per-finder basis. Table 6-8 lists the valid values.

Table 6-8 Finder Caching Options  
This Setting . . .  Causes Finders to . . .  When the Search Involves a Finder That . . . 

ConformResultsInUnitOfWork (default)

Check the Unit of Work cache before querying the session cache or the database. The finder's results always conform to uncommitted new objects, deleted objects, and changed objects.

Returns either a single bean or a collection

DoNotCheckCache

Query the database, bypassing the OracleAS TopLink internal caches.

Returns either a single bean or a collection

CheckCacheByExactPrimaryKey

Check the session cache for the object.

Contains only a primary key, and returns a single bean

CheckCacheByPrimaryKey

Check the session cache for the object.

Contains a primary key (and may contain other search parameters), and returns a single bean

CheckCacheThenDatabase

Search the session cache before accessing the database

Returns a single bean

CheckCacheOnly

Search against the session cache, but not the database.

Returns either a single bean or a collection

For more information about the OracleAS TopLink queries as well as the OracleAS TopLink Unit of Work and how it integrates with JTS, see Chapter 7, "Transactions".


Note:

To apply caching options to finders with manually created (findOneByQuery, findManyByQuery) queries, use the OracleAS TopLink API.


Disable Cache for Returned Finder Results

By default, OracleAS TopLink adds all returned objects to the session cache. However, if you know the set of returned objects is very large, and you want to avoid the expense of storing these objects, you can disable this behavior. To override the default configuration, implement the dontMaintainCache() call on the query, or disable returned object caching for the query in the OracleAS TopLink Mapping Workbench.

For more information about disabling caching for returned finder results, see the Oracle Application Server TopLink Mapping Workbench User's Guide.

Refreshing Finder Results

A finder may return information from the database for an object whose primary key is already in the cache. When set to true, the Refresh Cache option (in the OracleAS TopLink Mapping Workbench) causes the query to refresh the object's nonprimary key attributes with the returned information. This occurs on findByPrimaryKey finders as well as all expression and SQL finders for the bean.

If you build a query in Java code, you can set this option by including the refreshIdentityMapResult() method. This method automatically cascades changes to privately owned parts of the beans. If you require different behavior, configure the query using a dynamic finder instead.


Caution:

When you invoke this option from within a transaction, the refresh overwrites object attributes, including any that have not yet been written to the database.


If your application includes an OptimisticLock field, use the refresh cache option in conjunction with the onlyRefreshCacheIfNewerVersion() option. This ensures that the application refreshes objects in the cache only if the version of the object in the database is newer than the version in the cache.

For finders that have no refresh cache setting, the onlyRefreshCacheIfNewerVersion() method has no effect.

Managing Large Result Sets with Cursored Streams

Large result sets can be resource intensive to collect and process. To give the client more control over the returned results, configure OracleAS TopLink finders to use cursors. This combines OracleAS TopLink's CursoredStream with the ability of the database to cursor data, and breaks up the result set into smaller, more manageable pieces.

The behavior of a finder including a cursored stream differs from other finder as follows:

Building the Query

You can configure any finder that returns a java.util.Enumeration (under EJB 1.1) or a java.util.Collection (under EJB 2.0) to use a cursor. When you create the query for the finder, add the useCursoredStream() option to enable cursoring.

Example 6-84 Cursored Stream in a Finder

ReadAllQuery raq = new ReadAllQuery();
ExpressionBuilder bldr = new ExpressionBuilder();
raq.setReferenceClass(ProjectBean.class);
raq.useCursoredStream();
raq.addArgument("projectName");
raq.setSelectionCriteria(bldr.get("name").
like(bldr.getParameter("projectName")));
descriptor.getQueryManager().addQuery ("findByNameCursored", query);

Executing the Finder from the Client in EJB 1.1

OracleAS TopLink offers additional elements for traversing finder results. These elements include:

Example 6-85 illustrates client-code executing a cursored finder.

Example 6-85 Cursored Finder Under EJB 1.1

import oracle.toplink.ejb.cmpwls11. CursoredEnumerator;
//... other imports as necessary
getTransaction().begin();
CursoredEnumerator cursoredEnumerator = (CursoredEnumerator)getProjectHome()
 .findByNameCursored("proj%"); Vector projects = new Vector(); for (int index = 0; index < 50; i++) { Project project = (Project)cursoredEnumerator.nextElement(); projects.addElement(project); } // Rest all at once ... Vector projects2 = cursoredEnumerator.nextElements(50); cursoredEnumerator.close(); getTransaction().commit();
Executing the Finder from the Client in EJB 2.0

As with EJB 1.1, OracleAS TopLink offers additional elements for traversing finder results under EJB 2.0. These elements include:

OracleAS TopLink also offers an extended protocol for oracle.toplink.ejb.cmp.wls.CursoredIterator (based on java.util.Iterator):

Example 6-86 illustrates client code executing a cursored finder.

Example 6-86 Cursored Finder Under EJB 2.0

//import both CursoredCollection and CursoredIterator
import oracle.toplink.ejb.cmp.wls.*;
//... other imports as necessary
getTransaction().begin();
CursoredIterator cursoredIterator = (CursoredIterator) 
getProjectHome().findByNameCursored("proj%").iterator();
Vector projects = new Vector();
for (int index = 0; index < 50; i++) {
Project project = (Project)cursoredIterator.next();
projects.addElement(project);
}
// Rest all at once ...
Vector projects2 = cursoredIterator.next(50);
cursoredIterator.close();
getTransaction().commit();

Exception Handling

Most exceptions in queries are database exceptions, resulting from a failure in the database operation. Write operations can also throw an OptimisticLockException on a write, update, or delete operation in applications that use optimistic locking. To catch these exceptions, execute all database operations within a try-catch block.

{
    try {
        Vector employees = session.readAllObjects(Employee.class); 
        } 
    catch (DatabaseException exception) {
        // Handle exception 
        }
}

For more information about exceptions in a OracleAS TopLink application, see Appendix C, "Error Codes and Messages".


Go to previous page Go to next page
Oracle
Copyright © 2000, 2003 Oracle Corporation.

All Rights Reserved.
Go To Documentation Library
Home
Go To Product List
Solution Area
Go To Table Of Contents
Contents
Go To Index
Index