Fast Object Queries for Hibernate

Overcome performance and scalability problems of HQL queries without escaping to SQL: Write compact object queries with Hibernate and still get efficient SQL!



  • Select queries with navigation over arbitrarily many class relationships
  • Inheritance and polymorphism always assumed
  • Type specialization (downcasting) in navigation
  • Ordering and grouping
  • Unions of select queries
  • Objects, values, or aggregate functions in results
  • Windowing of the result set (offset and limit)
  • Parameterized queries


This is an informal and not strictly precise definition of the syntax, yet it is practically sufficient as a quick reminder. The syntax is generally very close to that of SQL, and the following description illustrates the supported features.

SELECT [DISTINCT] expression [[AS] alias], expression [[AS] alias]...
FROM term [[AS] alias], term [[AS] alias]...
[WHERE condition]
[GROUP BY expression [[AS] alias], expression [[AS] alias]...]
[HAVING condition]
[ORDER BY expression [ASC | DESC], expression [ASC | DESC]...]
[OFFSET offset]

Navigation terms

  • Class name (identifiers may be quoted with "", e.g. "from")


  • * (in SELECT only)
  • term.* (in SELECT only)
  • term
  • constant
  • #parameter#
  • arithmetic operators: +, -, *, /, MOD
  • aggregate functions: MAX, MIN, SUM, AVG, COUNT, COUNT(*), COUNT(DISTINCT)


  • equality and inequality: =, ==, !=, <>
  • comparison: <, <=, >, >=, BETWEEN ... AND ...
  • LIKE 'pattern', LIKE #param#
  • IN (constant, constant...)
  • Boolean operators: AND, OR, NOT


(type qualifier may be omitted if type can be inferred from the context):

  • Text: 'text'
  • Boolean: true, false
  • Integer:
    • decimal: 42
    • hexadecimal: 0x2A, x'2A'
    • octal: 052
    • binary: 0b101010, b'101010'
  • Decimal: 42.0, decimal'42.0', currency'42.0'
  • Real: 42.0, 6.022E+23, real'42.0'
  • Date: date'yyyy-MM-dd'
  • Time: time'HH:mm:ss.SSS'
  • DateTime: datetime'yyyy-MM-dd HH:mm:ss.SSS', timestamp'yyyy-MM-dd HH:mm:ss.SSS'
  • Enumerations: enumClassName'value' (e.g. Status'APPROVED')


This is just a brief description of the semantics of OQL queries, especially of those parts that are specific to OQL or not completely analogue to the counterparts in SQL. These explanations should be sufficient to those already familiar with SQL or HQL, while in-depth and precise explanations of the semantics of OQL queries can be found in the book: Milicev D., Model-Driven Development with Executable UML, Wiley/Wrox, July 2009, ISBN 9780470481639

The basic form of an OQL SELECT query is the same as in SQL:

SELECT projectionClause FROM navigationClause WHERE selectionClause

For example:

FROM Course c, c.students p
WHERE = 'Introduction to OQL'

Therefore, an OQL query has three main parts: the navigation clause (navigationClause following FROM), the selection clause (selectionClause following WHERE), and the projection clause (projectionClause following SELECT).

The navigation clause specifies navigation over the object structure. The navigation designates a type of tuples of values, each value representing a placeholder for an instance of a certain type. The navigations over properties also introduce implicit constraints over the returned tuples. The constraints enforce that the objects referred to by the placeholders are linked by links of the corresponding relationships. In the previous example, each tuple designated by the navigation clause has two values named c and p, where c is a placeholder for an object of Course, while p is a placeholder for an object of Person (the type of the navigated property students). The implicit restriction introduced by the navigation clause is that the objects referred to by p and c are linked, so that p is an element of the collection designated by c.students.

In short, the navigation clause defines a collection of tuples of values, one value for each term in the clause (the terms are separated by commas). The value in the tuple is named by the optional alias provided in the term, and can be referred to by that name in the clauses of the same query. Therefore, the navigation clause also introduces the names of the values in the tuple, the scope of the names being the same query.

As it can be concluded, the collection of tuples is obtained by the so-called inner join. (All OQL joins are inner joins. We will support outer joins in future versions.) This means that the collection of the tuples designated by the navigation clause consists of a sub-collection of a Cartesian product of all existing instances of the types, such that the values (referring to instances of classifiers) implied by the terms satisfy the following constraint: If a term in the clause specifies navigation over a property, the value implied from it must be an element of the collection designated by the referred property.

For the previous example, the resulting collection of tuples defined by the navigation clause is the collection of tuples (c, p), where c is an object of Course, while p is an object of Person (the type of the navigated property students), and p is an element of c.students. On the other hand, consider the tuple defined by the following FROM navigation clause, assuming that students of a course are objects of class Person, and the class Teacher is a subclass of the class Person:

Course c, c.students:Teacher t, n1, t.taughtCourses tc, n2

This consists of the named elements as shown in the following table:

NameTypeAdditional Implied Constraint
tTeachert is in c.students
n1Stringn1 is in
tcCoursetc is in t.taughtCourses
n2Stringn2 is in

As a result, the navigational clause defines a collection of all tuples retrieved from the object space satisfying the described criteria. In the general case, the collection is a bag, because the collections designated by the navigation terms can be bags, and the navigations can be combined over several properties (for example, t.taughtCourses.teachers.taughtCourses). Each value in the tuple is of the type defined in the term, and is always of cardinality exactly one (that is, it is never a null).

There are two kinds of terms allowed in the navigation clause.

The first kind of term is a simple class reference. The term simply specifies a class and an optional alias. An example is the term Course c. The type of the value designated by such a term is that class.

The second kind is a term that defines navigation over properties of objects. The navigation must start with a name defined by another term before this term. For example, the following terms define navigations:

c.students:Teacher t, n1, t.taughtCourses tc, n2

A term can include a navigation chain over several properties, as shown in the following example:


The number of hops over properties in a navigation chain can be arbitrary, but finite.

The type of the term is that of the last property in the navigation chain. Optionally, the type can be specialized to a subtype of the property’s type by the downcasting colon operator:


It specifies that the designated collection is restricted to objects of that subclass only (and all its subclasses; substitution/polymorphism is always assumed). The term above specifies the collection of all students of all courses taught by the given teacher t, but only those students that are also teachers (recall that the class Teacher is derived from the class Person).

The downcasting operator can appear anywhere in the navigation chain. In that case, the further navigation may refer to properties of the specializing type. For example, if t is of type Teacher:


The collection of values designated by a navigation term is obtained as an inner join of all elements in the chain. For example, the collection designated by this term is obtained by the following set of nested iterations:


For each instance t of the type specified by the term that defines t (Teacher in this case) — for each element e1 in t.taughtCourses, for each element e2 in e1.students, for each element e3 in e2.attendedCourses, return e3. It is obvious that if the chain has more than one navigation hop, the resulting collection can be non-unique (that is, a bag) in a general case, since the resulting values can be multiplied.

The selection clause after WHERE constrains further the resulting collection of tuples by specifying an expression. The expression must return a Boolean result. The expression can refer to all the names of the values in the tuple defined by the navigation clause, or to a parameter of the query. Parameters are arbitrary identifiers enclosed in # signs (for example, #nameParam#). Actual values of the parameters will be provided by the invocation of the query through an API call, when the query is executed. The WHERE expression is evaluated for every tuple in the collection designated by the navigation FROM clause. The resulting collection of tuples is reduced by the selection WHERE clause to all these and only these tuples for which the expression evaluates to true.

Finally, the projection clause following SELECT simply projects the resulting tuple to another tuple by keeping some of the values in the tuple and removing the others, or even adding new ones. Think of the collection of the tuples resulting from the rest of the query (the navigation and selection clauses) as a table with columns identifying values and rows representing the selected tuples. Then, the projection clause simply removes some columns, while keeping the others or adding new ones. The values can be the names of the terms from the navigation FROM clause or their properties. All rows are preserved (no duplicates are removed, unless the DISTINCT specifier is used). Optionally, the projection clause renames some columns by providing different names (aliases) for some values in the tuples.

The resulting collection of tuples can be grouped or ordered, as in SQL. The resulting set can be limited to a subcollection (a window) by providing the optional OFFSET (0-based, offset 0 is default) and LIMIT (the size of the window). For example, the following query returns the third group of 10 students (as objects) of the given course, ordered by their names:

SELECT p, p.*
FROM Course c, c.students p
WHERE = 'Introduction to OQL'

A union can be made of queries that have homogeneous result sets. This means that the tuples of the results of the queries in the union must have the same number of columns, and the corresponding columns must be of the same type or of a type with a common supertype; the resulting union will then have that common supertype as each column type. The resulting union is a true set union with distinct values (DISTINCT is default), unless the ALL specifier is used.