Movatterモバイル変換


[0]ホーム

URL:


A Guide to Hibernate Query Language

version 6.6.37.Final
Table of Contents

Preface

Hibernate 6 is a major redesign of the world’s most popular and feature-rich ORM solution.The redesign has touched almost every subsystem of Hibernate, including the APIs, mapping annotations, and, above all else, the query language.

This is the second time Hibernate Query Language has been completely reimplemented from scratch, but the first time in more than fifteen years.In this new incarnation, HQL is far more powerful, and the HQL compiler much more robust.

At long last, HQL has a feature set to match that of modern dialects of SQL, and is able to take full advantage of the power of modern SQL databases.

This document is a reference guide to the full feature set of the language, and is the only up-to-date source for those who wish to learn how to write HQL effectively in Hibernate 6.

If you are unfamiliar with Hibernate, be sure to first readIntroduction to Hibernate or check out theQuick Start.

1. Basic concepts

This document describes Hibernate Query Language (HQL), which is, I suppose we could say, a "dialect" of the Java (now Jakarta) Persistence Query Language (JPQL).

Or is it the other way around?

JPQL was inspired by early versions of HQL, and is a proper subset of modern HQL.Here we focus on describing the complete, more powerful HQL language as it exists today.

If strict JPA compliance is what you’re looking for, use the settinghibernate.jpa.compliance.query=true.With this configuration, any attempt to use HQL features beyond the JPQL subset will result in an exception.

We don’t recommend the use of this setting.

The truth is that HQL today has capabilities that go far beyond what is possible in plain JPQL.We’re not going to fuss too much about not limiting ourselves to the standard here.Faced with a choice between writing database-specific native SQL, or database-independent HQL, we know what our preference is.

1.1. HQL and SQL

Throughout this document, we’ll assume you know SQL and the relational model, at least at a basic level.HQL and JPQL are loosely based on SQL and are easy to learn for anyone familiar with SQL.

For example, if you understand this SQL query:

selectbook.title,pub.name/* projection */fromBookasbook/* root table */joinPublisheraspub/* table join */onbook.publisherId=pub.id/* join condition */wherebook.titlelike'Hibernate%'/* restriction (selection) */orderbybook.title/* sorting */

Then we bet you can already make sense of this HQL:

selectbook.title,pub.name/* projection */fromBookasbook/* root entity */joinbook.publisheraspub/* association join */wherebook.titlelike'Hibernate%'/* restriction (selection) */orderbybook.title/* sorting */

You might notice that even for this very simple example, the HQL version is slightly shorter.This is typical.Actually, HQL queries are usually much more compact than the SQL they compile to.

But there’s one huge difference: in HQL,Book refers to an entity class written in Java, andbook.title to a field of that class.We’re not permitted to directly reference database tables and columns in HQL or JPQL.

In this chapter, we’ll demonstrate how similar HQL is to SQL by giving a quick overview of the basic statement types.You’ll be bored to discover they’re exactly the ones you expect:select,insert,update, anddelete.

This is a reference guide.We’re not going to explain basic concepts like ternary logic, joins, aggregation, selection, or projection, because that information is freely available elsewhere, and anyway we couldn’t possibly do these topics justice here.If you don’t have a firm grasp of these ideas, it’s time to pick up a book about SQL or about the relational model.

But first we need to mention something that’s a bit different to SQL.HQL has a slightly complicated way of dealing with case sensitively.

1.2. Lexical structure

Lexically, JPQL is quite similar to SQL, so in this section we’ll limit ourselves to mentioning those places where it differs.

1.2.1. Identifiers and case sensitivity

An identifier is a name used to refer to an entity, an attribute of a Java class, anidentification variable, or a function.

For example,Book,title,author, andupper are all identifiers, but they refer to different kinds of things.In HQL and JPQL, the case sensitivity of an identifier depends on the kind of thing the identifier refers to.

The rules for case sensitivity are:

  • keywords and function names are case-insensitive, but

  • identification variable names, Java class names, and the names of attributes of Java classes, are case-sensitive.

We apologize for this inconsistency.In hindsight, it might have been better to define the whole language as case-sensitive.

Incidentally, it’s standard practice to use lowercase keywords in HQL.

The use of uppercase keywords indicates an endearing but unhealthy attachment to the culture of the 1970’s.

Just to reiterate these rules:

select,SeLeCT,sELEct, andSELECT

All the same,select is a keyword

upper(name) andUPPER(name)

Same,upper is a function name

from BackPack andfrom Backpack

Different, refer to different Java classes

person.nickName andperson.nickname

Different, since the path expression elementnickName refers to an attribute of an entity defined in Java

person.nickName,Person.nickName, andPERSON.nickName

All different, since the first element of a path expression is anidentification variable

The JPQL specification defines identification variables as case-insensitive.And so in strict JPA-compliant mode, Hibernate treatsperson.nickName,Person.nickName, andPERSON.nickName as thesame.

Aquoted identifier is written in backticks. Quoting lets you use a keyword as an identifier.

selectthing.interval.`from`fromThingthing

Actually, in most contexts, HQL keywords are "soft", and don’t need to be quoted.The parser is usually able to distinguish if the reserved word is being used as a keyword or as an identifier.

1.2.2. Comments

Comments in HQL look like multiline comments in Java.They’re delimited by/* and*/.

Neither SQL-style-- nor Java-style// line-ending comments are allowed.

It’s quite rare to see comments in HQL, but perhaps it will be more common now that Java has text blocks.

1.2.3. Parameters

Parameters come in two flavors in JPQL, and HQL supports a third flavor for historical reasons:

Parameter typeExamplesUsage from Java

Named parameters

:name,:title,:id

query.setParameter("name", name)

Ordinal parameters

?1,?2,?3

query.setParameter(1, name)

JDBC-style parameters 💀

?

query.setParameter(1, name)

JDBC-style parameters of form? are like ordinal parameters where the index is inferred from the position in the text of the query.JDBC-style parameters are deprecated.

It’sextremely important to use parameters to pass user input to the database.Constructing a query by concatenating HQL fragments with user input is extremely dangerous, opening the door to the possibility of executing arbitrary code on the database server.

1.2.4. Literals

Some of the syntax for literal values also departs from the standard syntax in ANSI SQL, especially in the area of date/time literals, but we’ll discuss all that later, inLiterals.

1.3. Syntax

We’ll describe the syntax of the language as we go along, sometimes displaying fragments of the grammar in an ANTLR-like BNF form.(Occasionally we’ll simplify these snippets for readability, so please don’t take them as canonical.)

The full canonical grammar for HQL can be found inthe github project.

The grammar for JPQL may be found in chapter 4 of the JPA specification.

1.4. Type system

JPA doesn’t have a well-specified type system, but, reading between the lines a bit, the following types may be discerned:

  • entity types,

  • numeric values,

  • strings,

  • dates/times,

  • booleans, and

  • enumerated types.

Such a coarse-grained type system is in some sense an insufficient constraint on implementors of the specification, or, viewed from a different perspective, it leaves us quite a lot of flexibility.

The way HQL interprets this type system is to assign a Java type to every expression in the language.Thus, numeric expressions have types likeLong,Float, orBigInteger, date/time expressions have types likeLocalDate,LocalDateTime, orInstant, and boolean expressions are always of typeBoolean.

Going further, an expression likelocal datetime - document.created is assigned the Java typejava.time.Duration, a type which doesn’t appear anywhere in the JPA specification.

Since the language must be executed on SQL databases, every type accommodates null values.

1.4.1. Null values and ternary logic

The SQLnull behaves quite differently to a null value in Java.

  • In Java, an expression likenumber + 1 produces in an exception ifnumber is null.

  • But in SQL, and therefore also in HQL and JPQL, such an expression evaluates tonull.

It’s almost always the case that an operation applied to a null value yields another null value.This rule applies to function application, to operators like* and||, to comparison operators like< and=, and even to logical operations likeand andnot.

The exceptions to this rule are theis null operator and the functionscoalesce() andifnull() which are specifically designed fordealing with null values.

This rule is the source of the famous (and controversial)ternary logic of SQL.A logical expression likefirstName='Gavin' and team='Hibernate' isn’t restricted to the valuestrue andfalse.It may also benull.

This can, in principle, lead to some quite unintuitive results: we can’t use the law of the excluded middle to reason about logical expressions in SQL!But in practice, we’ve never once run into a case where this caused us problems.

As you probably know, when a logical predicate occurs as arestriction, rows for which the predicate evaluates tonull areexcluded from the result set.That is, in this context at least, a logical null is interpreted as "effectively false".

1.5. Statement types

HQL features four different kinds of statement:

  • select queries,

  • update statements,

  • delete statements, and

  • insert …​ values andinsert …​ select statements.

Collectively,insert,update, anddelete statements are sometimes calledmutation queries.We need to be a little bit careful when executing mutation queries via a stateful session.

The effect of anupdate ordelete statement is not reflected in the persistence context, nor in the state of entity objects held in memory at the time the statement is executed.

It’s the responsibility of the client program to maintain synchronization of state held in memory with the database after execution of anupdate ordelete statement.

Let’s consider each type of mutation query in turn, beginning with the most useful type.

1.5.1. Update statements

TheBNF for anupdate statement is quite straightforward:

updateStatement    : "UPDATE" "VERSIONED"? targetEntity setClause whereClause?targetEntity: entityName variable?setClause: "SET" assignment ("," assignment)*assignment    : simplePath "=" expression

Theset clause has a list of assignments to attributes of the given entity.

For example:

updatePersonsetnickName='Nacho'wherename='Ignacio'

Update statements are polymorphic, and affect mapped subclasses of the given entity class.Therefore, a single HQLupdate statement might result in multiple SQL update statements executed against the database.

Anupdate statement must be executed usingQuery.executeUpdate().

// JPA APIintupdatedEntities=entityManager.createQuery("update Person p set p.name = :newName where p.name = :oldName").setParameter("oldName",oldName).setParameter("newName",newName).executeUpdate();
// Hibernate native APIintupdatedEntities=session.createMutationQuery("update Person set name = :newName where name = :oldName").setParameter("oldName",oldName).setParameter("newName",newName).executeUpdate();

The integer value returned byexecuteUpdate() indicates the number of entity instances affected by the operation.

In aJOINED inheritance hierarchy, multiple rows are required to store a single entity instance.In this case, the update count returned by Hibernate might not be exactly the same as the number of rows affected in the database.

Anupdate statement, by default, does not affect the column mapped by the@Version attribute of the affected entities.

Adding the keywordversioned—writingupdate versioned—specifies that Hibernate should increment the version number or update the last modification timestamp.

updateversionedBooksettitle=:newTitlewheressn=:ssn

Anupdate statement may not directlyjoin other entities, but it may:

  • have animplicit join, or

  • have subqueries in itsset clause, or in itswhere clause, and the subqueries may contain joins.

1.5.2. Delete statements

The BNF for adelete statement is even simpler:

deleteStatement    : "DELETE" "FROM"? targetEntity whereClause?

For example:

deleteAuthorauthorwhereisemptyauthor.books

As in SQL, the presence or absence of thefrom keyword has absolutely no effect on the semantics of thedelete statement.

Just like update statements, delete statements are polymorphic, and affect mapped subclasses of the given entity class.Therefore, a single HQLdelete statement might result in multiple SQL delete statements executed against the database.

Adelete statement is executed by callingQuery.executeUpdate().

The integer value returned byexecuteUpdate() indicates the number of entity instances affected by the operation.

Adelete statement may not directlyjoin other entities, but it may:

  • have animplicit join, or

  • have subqueries in itswhere clause, and the subqueries may contain joins.

1.5.3. Insert statements

There are two kinds ofinsert statement:

  • insert …​ values, where the attribute values to insert are given directly as tuples, and

  • insert …​ select, where the inserted attribute values are sourced from a subquery.

The first form inserts a single row in the database, or multiple rows if you provide multiple tuples in thevalues clause.The second form may insert many new rows, or none at all.

The first sort ofinsert statement is not as useful.It’s usually better to just usepersist().

But you might consider using it to set up test data.

insert statements are not part of JPQL.

The BNF for aninsert statement is:

insertStatement    : "INSERT" "INTO"? targetEntity targetFields      (queryExpression | valuesList)      conflictClause?targetEntity: entityName variable?targetFields: "(" simplePath ("," simplePath)* ")"valuesList: "VALUES" values ("," values)*values: "(" expression ("," expression)* ")"

For example:

insertPerson(id,name)values(100L,'Jane Doe'),(200L,'John Roe')
insertintoAuthor(id,name,bio)selectid,name,name||' is a newcomer for '||str(year(localdate))fromPersonwhereid=:pid

As in SQL, the presence or absence of theinto keyword has no effect on the semantics of theinsert statement.

From these examples we might notice thatinsert statements are in one respect a bit different toupdate anddelete statements.

Aninsert statement is inherentlynot polymorphic!Its list of target fields is of fixed length, whereas each subclass of an entity class might declare additional fields.If the entity is involved in a mapped inheritance hierarchy, only attributes declared directly by the named entity and its superclasses may occur in the list of target fields.Attributes declared by subclasses may not occur.

ThequeryExpression in aninsert …​ select statement may be any validselect query, with the caveat that the types of the values in theselect list must match the types of the target fields.

This is checked during query compilation rather than allowing the type check to delegate to the database.This may cause problems when two Java types map to the same database type.For example, an attribute of typeLocalDateTime and an attribute or typeTimestamp both map to the SQL typetimestamp, but are not considered assignable by the query compiler.

There are two ways to assign a value to the@Id attribute:

  • explicitly specify the id attribute in the list of target fields, and its value in the values assigned to the target fields, or

  • omit it, in which case a generated value is used.

Of course, the second option is only available for entities with database-level id generation (sequences or identity/autoincrement columns).It’s not available for entities whose id generator is implemented in Java, nor for entities whose id is assigned by the application.

The same two options are available for a@Version attribute.When no version is explicitly specified, the version for a new entity instance is used.

Theon conflict clause lets us specify what action should be taken when the database already contains the record we’re attempting to insert.

conflictClause: ON CONFLICT conflictTarget? "DO" conflictActionconflictTarget: ON CONSTRAINT identifier| "(" simplePath ("," simplePath)* ")"conflictAction: "NOTHING"| "UPDATE" setClause whereClause?

Note that theon constraint variant accepting the name of a unique constraint only works on certain databases, or when just a single row is being inserted.

insertPerson(ssn,name,phone)values('116-76-1234','Jane Doe','404 888 4319')onconflict(ssn)doupdatesetphone=excluded.phone

Likeupdate anddelete statements, aninsert statement must be executed by callingQuery.executeUpdate().

Now it’s time to look at somethingmuch more complicated.

1.5.4. Select statements

Select statements retrieve and analyse data.This is what we’re really here for.

The full BNF for aselect query is quite complicated, but there’s no need to understand it now.We’re displaying it here for future reference.

selectStatement: queryExpressionqueryExpression: withClause? orderedQuery (setOperator orderedQuery)*orderedQuery: (query | "(" queryExpression ")") queryOrder?query: selectClause fromClause? whereClause? (groupByClause havingClause?)?| fromClause whereClause? (groupByClause havingClause?)? selectClause?| whereClausequeryOrder: orderByClause limitClause? offsetClause? fetchClause?fromClause: "FROM" entityWithJoins ("," entityWithJoins)*entityWithJoins: fromRoot (join | crossJoin | jpaCollectionJoin)*fromRoot: entityName variable?| "LATERAL"? "(" subquery ")" variable?join: joinType "JOIN" "FETCH"? joinTarget joinRestriction?joinTarget: path variable?| "LATERAL"? "(" subquery ")" variable?withClause: "WITH" cte ("," cte)*;

Most of the complexity here arises from the interplay of set operators (union,intersect, andexcept) with sorting.

We’ll describe the various clauses of a query later, inRoot entities and joins and inSelection, projection, and aggregation, but for now, to summarize, a query might have these bits:

ClauseJargonPurpose

with

Common table expressions

Declaresnamed subqueries to be used in the following query

from andjoin

Roots and joins

Specifies the entities involved in the query, and how they’rerelated to each other

where

Selection/restriction

Specifies arestriction on the data returned by the query

group by

Aggregation/grouping

Controlsaggregation

having

Selection/restriction

Specifies arestriction to applyafter aggregation

select

Projection

Specifies aprojection (the things to return from the query)

union,intersect,except

Set algebra

These areset operators applied to the results of multiple subqueries

order by

Ordering

Specifies how the results should besorted

limit,offset,fetch

Limits

Allows forlimiting or paginating the results

Every one of these clauses is optional!

For example, the simplest query in HQL has noselect clause at all:

fromBook

But we don’t necessarilyrecommend leaving off theselect list.

HQL doesn’t require aselect clause, but JPQLdoes.

Naturally, the previous query may be written with aselect clause:

selectbookfromBookbook

But when there’s no explicitselect clause, the select list is implied by the result type of the query:

// result type Book, only the Book selectedList<Book>books=session.createQuery("from Book join authors",Book.class).getResultList();for(Bookbook:books){...}
// result type Object[], both Book and Author selectedList<Object[]>booksWithAuthors=session.createQuery("from Book join authors",Book.class,Object[].class).getResultList();for(varbookWithAuthor:booksWithAuthors){Bookbook=(Book)bookWithAuthor[0];Authorauthor=(Author)bookWithAuthor[1];...}

For complicated queries, it’s probably best to explicitly specify aselect list.

An alternative "simplest" query hasonly aselect list:

selectlocaldatetime

This results in a SQLfrom dual query (or equivalent).

Looking carefully at the BNF given above, you might notice that theselect list may occur either at the beginning of a query, or near the end, right beforeorder by.

Of course, standard SQL, and JPQL, require that theselect list comes at the beginning.But it’s more natural to put it last:

fromBookbookselectbook.title,book.isbn

This form of the query is more readable, because the alias is declaredbefore it’s used, just as God and nature intended.

Naturally, queries are always polymorphic.Indeed, a fairly innocent-looking HQL query can easily translate to a SQL statement with many joins and unions.

We need to be abit careful about that, but actually it’s usually a good thing.HQL makes it very easy to fetch all the data we need in a single trip to the database, and that’s absolutely key to achieving high performance in data access code.Typically, it’s much worse to fetch exactly the data we need, but in many round trips to the database server, than it is to fetch just a bit more data than what we’re going to need, all a single SQL query.

When there’s no explicitselect clause, a further abbreviation is sometimes possible.

When the result type of aselect query is an entity type, and we specify the type explicitly by passing the entity class tocreateQuery() orcreateSelectionQuery(), we’re sometimes allowed to omit thefrom clause, for example:

// explicit result type Book, so 'from Book' is inferredList<Book>books=session.createQuery("where title like :title",Book.class).setParameter("title",title).getResultList();

1.6. Representing result sets in Java

One of the most uncomfortable aspects of working with data in Java is that there’s no good way to represent a table.Languages designed for working with data—R is an excellent example—always feature some sort of built-in table or "data frame" type.Of course, Java’s type system gets in the way here.This problem is much easier to solve in a dynamically-typed language.The fundamental problem for Java is that it doesn’t have tuple types.

Queries in Hibernate return tables.Sure, often a column holds whole entity objects, but we’re not restricted to returning a single entity, and we often write queries that return multiple entities in each result, or which return things which aren’t entities.

So we’re faced with the problem if representing such result sets, and, we’re sad to say, there’s no fully general and completely satisfying solution.

Let’s begin with the easy case.

1.6.1. Queries with a single projected item

If there’s just one projected item in theselect list, then, no sweat, that’s the type of each query result.

List<String>results=entityManager.createQuery("select title from Book",String.class).getResultList();

There’s really no need to fuss about with trying to represent a "tuple of length 1".We’re not even sure what to call those.

Problems arise as soon as we have multiple items in theselect list of a query.

1.6.2. Queries with multiple projected items

When there are multiple expressions in the select list then, by default, and in compliance with JPA, each query result is packaged as an array of typeObject[].

List<Object[]>results=entityManager.createQuery("select title, left(book.text, 200) from Book",Object[].class).getResultList();for(varresult:results){Stringtitle=(String)result[0];Stringpreamble=(String)result[1];}

This is bearable, but let’s explore some other options.

JPA lets us specify that we want each query result packaged as an instance ofjakarta.persistence.Tuple.All we have to do is pass the classTuple tocreateQuery().

List<Tuple>tuples=entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",Tuple.class).getResultList();for(Tupletuple:tuples){Stringtitle=tuple.get("title",String.class);Stringpreamble=tuple.get("preamble",String.class);}

The names of theTuple elements are determined by the aliases given to the projected items in the select list.If no aliases are specified, the elements may be accessed by their position in the list, where the first item is assigned the position zero.

As an extension to JPA, and in a similar vein, Hibernate lets us passMap orList, and have each result packaged as a map or list:

varresults=entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",Map.class).getResultList();for(varmap:results){Stringtitle=(String)map.get("title");Stringpreamble=(String)map.get("preamble");}
varresults=entityManager.createQuery("select title, left(book.text, 200) from Book",List.class).getResultList();for(varlist:results){Stringtitle=(String)list.get(0);Stringpreamble=(String)list.get(1);}

Unfortunately, not one of the typesObject[],List,Map, norTuple lets us access an individual item in a result tuple without a type cast.SureTuple does the type cast for us when we pass a class object toget(), but it’s logically identical.Fortunately there’s one more option, as we’re about to see.

Actually,Tuple really exists to service the criteria query API, and in that context itdoes enable truly typesafe access to query results.

Hibernate 6 lets us pass an arbitrary class type with an appropriate constructor tocreateQuery() and will use it to package the query results.This works extremely nicely withrecord types.

recordBookSummary(Stringtitle,Stringsummary){}List<BookSummary>results=entityManager.createQuery("select title, left(book.text, 200) from Book",BookSummary.class).getResultList();for(varresult:results){Stringtitle=result.title();Stringpreamble=result.summary();}

It’s important that the constructor ofBookSummary has parameters which exactly match the items in theselect list.

This class does not need to be mapped or annotated in any way.

Even if the classis an entity class, the resulting instances arenot managed entities and arenot associated with the session.

We must caution that this still isn’t typesafe.In fact, we’ve just pushed the typecasts down into the call tocreateQuery().But at least we don’t have to write them explicitly.

1.6.3. Instantiation

In JPQL, and in older versions of Hibernate, this functionality required more ceremony.

Result typeLegacy syntaxStreamlined syntaxJPA standard

Map

select new map(x, y)

select x, y

✖/✖

List

select new list(x, y)

select x, y

✖/✖

Arbitrary classRecord

select new Record(x, y)

select x, y

✔/✖

For example, the JPA-standardselect new construct packages the query results into a user-written Java class instead of an array.

recordBookSummary(Stringtitle,Stringsummary){}List<BookSummary>results=entityManager.createQuery("select new BookSummary(title, left(book.text, 200)) from Book",BookSummary.class).getResultList();for(varresult:results){Stringtitle=result.title();Stringpreamble=result.summary();}

Simplifying slightly, the BNF for a projected item is:

selection    : (expression | instantiation) alias?instantiation    : "NEW" instantiationTarget "(" selection ("," selection)* ")"alias    : "AS"? identifier

Where the list ofselections in aninstantiation is essentially a nested projection list.

2. Expressions

We now switch gears, and begin describing the language from the bottom up.The very bottom of a programming language is its syntax for literal values.

2.1. Literals

The most important literal value in this language isnull. It’s assignable to any other type.

2.1.1. Boolean literals

The boolean literal values are the (case-insensitive) keywordstrue andfalse.

2.1.2. String literals

String literals are enclosed in single quotes.

select'hello world'

To escape a single quote within a string literal, use a doubled single quote:''.

fromBookwheretitlelike'Ender''s'

Alternatively, Java-style double-quoted strings are also allowed, with the usual Java character escape syntax.

select"hello\tworld"

This option is not much used.

2.1.3. Numeric literals

Numeric literals come in several different forms:

KindTypeExample

Integer literals

Long,Integer,BigInteger

1,3_000_000L,2BI

Decimal literals

Double,Float,BigDecimal

1.0,123.456F,3.14159265BD

Hexadecimal literals

Long,Integer

0X1A2B,0x1a2b

Scientific notation

Double,Float,BigDecimal

1e-6,6.674E-11F

For example:

fromBookwhereprice<100.0
selectauthor,count(book)fromAuthorasauthorjoinauthor.booksasbookgroupbyauthorhavingcount(book)>10

The type of a numeric literal may be specified using a Java-style postfix:

PostfixTypeJava type

L orl

long integer

long

D ord

double precision

double

F orf

single precision

float

BI orbi

large integer

BigInteger

BD orbd

exact decimal

BigDecimal

It’s not usually necessary to specify the precision explicitly.

In a literal with an exponent, theE is case-insensitive.Similarly, the Java-style postfix is case-insensitive.

2.1.4. Date and time literals

According to the JPQL specification, date and time literals may be specified using the JDBC escape syntax.Since this syntax is rather unpleasant to look at, HQL provides not one, but two alternatives.

Date/time typeRecommended Java typeJDBC escape syntax 💀Braced literal syntaxExplicitly typed literal syntax

Date

LocalDate

{d 'yyyy-mm-dd'}

{yyyy-mm-dd}

date yyyy-mm-dd

Time

LocalTime

{t 'hh:mm'}

{hh:mm}

time hh:mm

Time with seconds

LocalTime

{t 'hh:mm:ss'}

{hh:mm:ss}

time hh:mm:ss

Datetime

LocalDateTime

{ts 'yyyy-mm-ddThh:mm:ss'}

{yyyy-mm-dd hh:mm:ss}

datetime yyyy-mm-dd hh:mm:ss

Datetime with milliseconds

LocalDateTime

{ts 'yyyy-mm-ddThh:mm:ss.millis'}

{yyyy-mm-dd hh:mm:ss.millis}

datetime yyyy-mm-dd hh:mm:ss.millis

Datetime with an offset

OffsetDateTime

{ts 'yyyy-mm-ddThh:mm:ss+hh:mm'}

{yyyy-mm-dd hh:mm:ss +hh:mm}

datetime yyyy-mm-dd hh:mm:ss +hh:mm

Datetime with a time zone

OffsetDateTime

{ts 'yyyy-mm-ddThh:mm:ss GMT'}

{yyyy-mm-dd hh:mm:ss GMT}

datetime yyyy-mm-dd hh:mm:ss GMT

Literals referring to the current date and time are also provided.Again there is some flexibility.

Date/time typeJava typeUnderscored syntaxSpaced syntax

Date

java.time.LocalDate

local_date

local date

Time

java.time.LocalTime

local_time

local time

Datetime

java.time.LocalDateTime

local_datetime

local datetime

Offset datetime

java.time.OffsetDateTime

offset_datetime

offset datetime

Instant

java.time.Instant

instant

instant

Date

java.sql.Date 💀

current_date

current date

Time

java.sql.Time 💀

current_time

current time

Datetime

java.sql.Timestamp 💀

current_timestamp

current timestamp

Of these, onlylocal date,local time,local datetime,current_date,current_time, andcurrent_timestamp are defined by the JPQL specification.

The use of date and time types from thejava.sql package is strongly discouraged!Always usejava.time types in new code.

2.1.5. Duration literals

There are two sorts of duration in HQL:

  • year-day durations, that is, the length of an interval between two dates, and

  • week-nanosecond durations, that is, the length of an interval between two datetimes.

For conceptual reasons, the two kinds of duration cannot be cleanly composed.

Literal duration expressions are of formn unit, for example1 day or10 year or100 nanosecond.

selectstart,end,start-30minutefromEvent

The unit may be:day,week,month,quarter,year,second,minute,hour, ornanosecond.

A HQL duration is considered to map to a Javajava.time.Duration, but semantically they’re perhaps more similar to an ANSI SQLINTERVAL type.

2.1.6. Binary string literals

HQL also provides a choice of formats for binary strings:

  • the braced syntax{0xDE, 0xAD, 0xBE, 0xEF}, a list of Java-style hexadecimal byte literals, or

  • the quoted syntaxX’DEADBEEF' orx’deadbeef', similar to SQL.

2.1.7. Enum literals

Literal values of a Java enumerated type may be written without needing to specify the enum class name:

fromBookwherestatus<>OUT_OF_PRINT
fromBookwheretypein(BOOK,MAGAZINE)
updateBooksetstatus=OUT_OF_PRINT

In the examples above, the enum class is inferred from the type of the expression on the left of the comparison, assignment operator, orin predicate.

fromBookorderbycasetypewhenBOOKthen1whenMAGAZINEthen2whenJOURNALthen3else4end

In this example the enum class is inferred from the type of thecase expression.

2.1.8. Java constants

HQL allows any Javastatic constant to be used in HQL, but it must be referenced by its fully-qualified name:

selectjava.lang.Math.PI

2.1.9. Literal entity names

Entity names may also occur as a literal value. They do not need to be qualified.

fromPaymentaspaymentwheretype(payment)=CreditCardPayment

2.2. Identification variables and path expressions

A path expression is either:

  • a reference to anidentification variable, or

  • acompound path, beginning with a reference to an identification variable, and followed by a period-separated list of references to entity attributes.

As an extension to the JPA spec, HQL, just like SQL, allows a compound path expression where the identification variable at the beginning of the path is missing.That is, instead ofvar.foo.bar, it’s legal to write justfoo.bar.But this is only allowed when the identification variable may be unambiguously inferred from the first element,foo of the compound path.The query must have exactly one identification variablevar for which the pathvar.foo refers to an entity attribute.Note that we will continue to call these paths "compound", even if they only have one element.

This streamlines the query rather nicely when there’s just one root entity and no joins.But when the query has multiple identification variables it makes the query much harder to understand.

If an element of a compound path refers to an association, the path expression produces animplicit join.

selectbook.publisher.namefromBookbook

An element of a compound path referring to a many-to-one or on-to-one association may have thetreat function applied to it.

selecttreat(order.paymentasCreditCardPayment).creditCardNumberfromOrderorder

If an element of a compound path refers to a collection or many-valued association, it must have one ofthese special functions applied to it.

selectelement(book.authors).namefromBookbook

No other function may be applied to a non-terminal element of a path expression.

Alternatively, if the element of the compound path refers to a list or map, it may have the indexing operator applied to it:

selectbook.editions[0].datefromBookbook

No other operator may be applied to a non-terminal element of a path expression.

2.3. Operator expressions

HQL has operators for working with strings, numeric values, and date/time types.

The operator precedence is given by this table, from highest to lowest precedence:

Precedence classTypeOperators

Grouping and tuple instantiation

( …​ ) and(x, y, z)

Case lists

case …​ end

Member reference

Binary infix

a.b

Function application

Postfix

f(x,y)

Indexing

Postfix

a[i]

Unary numeric

Unary prefix

+,-

Duration conversions

Unary postfix

by day and friends

Binary multiplicative

Binary infix

*,/,%

Binary additive

Binary infix

+,-

Concatenation

Binary infix

||

Nullness, emptiness, truth

Unary postfix

is null,is empty,is true,is false

Containment

Binary infix

in,not in

Between

Ternary infix

between,not between

Pattern matching

Binary infix

like,ilike,not like,not ilike

Comparison operators

Binary infix

=,<>,<,>,<=,>=

Nullsafe comparison

Binary infix

is distinct from,is not distinct from

Existence

Unary prefix

exists

Membership

Binary infix

member of,not member of

Logical negation

Unary prefix

not

Logical conjunction

Binary infix

and

Logical disjunction

Binary infix

or

2.3.1. String concatenation

HQL defines two ways to concatenate strings:

  • the SQL-style concatenation operator,||, and

  • the JPQL-standardconcat() function.

Seebelow for details of theconcat() function.

selectbook.title||' by '||listagg(author.name,' & ')fromBookasbookjoinbook.authorsasauthorgroupbybook

Many more operations on strings are defined below, inFunctions.

2.3.2. Numeric arithmetic

The basic SQL arithmetic operators,+,-,*, and/ are joined by the remainder operator%.

select(1.0+:taxRate)*sum(item.book.price*item.quantity)fromOrderasordjoinord.itemsasitemwhereord.id=:oid

When both operands of a binary numeric operator have the same type, the result type of the whole expression is the same as the operands.

By default, the semantics of integer division depend on the database:

  • On most databases, division of an integer by an integer evaluates to an integer, just like in Java.Thus,3/2 evaluates to1.

  • But on some databases, including Oracle, MySQL, and MariaDB, integer division may result in a non-integral value.So3/2 evaluates to1.5 on these databases.

This default behavior may be changed using configuration propertyhibernate.query.hql.portable_integer_division.Setting this property totrue instructs Hibernate to produce SQL that emulates Java-style integer division (that is,3/2 = 1) on platforms where that is not the native semantics.

When the operands are of different type, one of the operands is implicitly converted towider type, with wideness given, in decreasing order, by the list below:

  • Double (widest)

  • Float

  • BigDecimal

  • BigInteger

  • Long

  • Integer

  • Short

  • Byte

Many more numeric operations are defined below, inFunctions.

2.3.3. Datetime arithmetic

Arithmetic involving dates, datetimes, and durations is quite subtle.Among the issues to consider are:

  • There’s two kinds of duration: year-day, and week-nanosecond durations.The first is a difference between dates; the second is a difference between datetimes.

  • We can subtract dates and datetimes, but we can’t add them.

  • A Java-style duration has much too much precision, and so in order to use it for anything useful, we must somehow truncate it to something coarser-grained.

Here we list the basic operations.

OperatorExpression typeExampleResulting type

-

Difference between two dates

your.birthday - local date

year-day duration

-

Difference between two datetimes

local datetime - record.lastUpdated

week-nanosecond duration

-

Difference of a date and a year-day duration

local date - 1 day

date

-

Difference of a datetime and a week-nanosecond duration

record.lastUpdated - 1 minute

datetime

-

Difference between two durations

1 week - 1 day

duration

+

Sum of a date and a year-day duration

local date + 1 week

date

+

Sum of a datetime and a week-nanosecond duration

record.lastUpdated + 1 second

datetime

+

Sum of two durations

1 day + 4 hour

duration

*

Product of an integer and a duration

billing.cycles * 30 day

duration

by unit

Convert a duration to an integer

(1 year) by day

integer

Theby unit operator converts a duration to an integer, for example:(local date - your.birthday) by day evaluates to the number of days you still have to wait.

The functionextract(unit from …​) extracts a field from a date, time, or datetime type, for example,extract(year from your.birthday) produces the year in which you were born, and throws away important information about your birthday.

Please carefully note the difference between these two operations:by andextract() both evaluate to an integer, but they have very different uses.

Additional datetime operations, including the usefulformat() function, are defined below, inFunctions.

2.4. Case expressions

Just like in standard SQL, there are two forms of case expression:

  • thesimple case expression, and

  • the so-calledsearched case expression.

Case expressions are verbose.It’s often simpler to use thecoalesce(),nullif(), orifnull() functions,as described below inFunctions for working with null values.

Simple case expressions

The syntax of the simple form is defined by:

"CASE" expression ("WHEN" expression "THEN" expression)+ ("ELSE" expression)? "END"

For example:

selectcaseauthor.nomDePlumewhen''thenperson.nameelseauthor.nomDePlumeendfromAuthorasauthorjoinauthor.personasperson
Searched case expressions

The searched form has the following syntax:

"CASE" ("WHEN" predicate "THEN" expression)+ ("ELSE" expression)? "END"

For example:

selectcasewhenauthor.nomDePlumeisnullthenperson.nameelseauthor.nomDePlumeendfromAuthorasauthorjoinauthor.personasperson

Acase expression may contain complex expression, including operator expressions.

2.5. Tuples

Atuple instantiation is an expression like(1, 'hello'), and may be used to "vectorize" comparison expressions.

fromPersonwhere(firstName,lastName)=('Ludwig','Boltzmann')
fromEventwhere(year,day)>(year(localdate),day(localdate))

This syntax may be used even when the underlying SQL dialect doesnot support so-called "row value" constructors.

A tuple value may be compared to an embedded field:

fromPersonwhereaddress=('1600 Pennsylvania Avenue, NW','Washington','DC',20500,'USA')

2.6. Functions

Both HQL and JPQL define some standard functions and make them portable between databases.

A program that wishes to remain portable between Jakarta Persistence providers should in principle limit itself to the use of the functions which are blessed by the specification.Unfortunately, there’s not so many of them.

In some cases, the syntax of these functions looks a bit funny at first, for example,cast(number as String), orextract(year from date), or eventrim(leading '.' from string).This syntax is inspired by standard ANSI SQL, and we promise you’ll get used to it.

HQL abstracts away from the actual database-native SQL functions, letting you write queries which are portable between databases.

For some functions, and always depending on the database, a HQL function invocation translates to a quite complicated SQL expression!

In addition, there are several ways to use a database function that’s not known to Hibernate.

2.6.1. Types and typecasts

The following special functions make it possible to discover or narrow expression types:

Special functionPurposeSignatureJPA standard

type()

The (concrete) entity or embeddable type

type(e)

treat()

Narrow an entity or embeddable type

treat(e as Entity)

cast()

Narrow a basic type

cast(x as Type)

str()

Cast to a string

str(x)

Let’s see what these functions do.

Evaluating an entity type

The functiontype(), applied to an identification variable or to an entity-valued or embeddable-valued path expression, evaluates to the concrete type, that is, the JavaClass, of the referenced entity or embeddable.This is mainly useful when dealing with entity inheritance hierarchies.

selectpaymentfromPaymentaspaymentwheretype(payment)=CreditCardPayment
Narrowing an entity type

The functiontreat() may be used to narrow the type of an identification variable.This is useful when dealing with entity or embeddable inheritance hierarchies.

selectpaymentfromPaymentaspaymentwherelength(treat(paymentasCreditCardPayment).cardNumber)between16and20

The type of the expressiontreat(p as CreditCardPayment) is the narrowed type,CreditCardPayment, instead of the declared typePayment ofp.This allows the attributecardNumber declared by the subtypeCreditCardPayment to be referenced.

  • The first argument is usually an identification variable.

  • The second argument is the target type given as an unqualified entity name.

Thetreat() function may even occur in ajoin.

General typecasts

The functioncast() has a similar syntax, but is used to narrow basic types.

  • Its first argument is usually an attribute of an entity, or a more complex expression involving entity attributes.

  • Its second argument is the target type given as an unqualified Java class name:String,Long,Integer,Double,Float,Character,Byte,BigInteger,BigDecimal,LocalDate,LocalTime,LocalDateTime, etc.

selectcast(idasString)fromOrder
Casting to string

The functionstr(x) is a synonym forcast(x as String).

selectstr(id)fromOrder

2.6.2. Functions for working with null values

The following functions make it easy to deal with null values:

FunctionPurposeSignatureJPA standard

coalesce()

First non-null argument

coalesce(x, y, z)

ifnull()

Second argument if first is null

ifnull(x,y)

nullif()

null if arguments are equal

nullif(x,y)

Handling null values

Thecoalesce() function is a sort of abbreviatedcase expression that returns the first non-null operand.

selectcoalesce(author.nomDePlume,person.name)fromAuthorasauthorjoinauthor.personasperson
Handling null values

HQL allowsifnull() as a synonym forcoalesce() in the case of exactly two arguments.

selectifnull(author.nomDePlume,person.name)fromAuthorasauthorjoinauthor.personasperson
Producing null values

On the other hand,nullif() evaluates to null if its operands are equal, or to its first argument otherwise.

selectifnull(nullif(author.nomDePlume,person.name),'Real name')fromAuthorasauthorjoinauthor.personasperson

2.6.3. Functions for working with dates and times

There are some very important functions for working with dates and times.

Special functionPurposeSignatureJPA standard

extract()

Extract a datetime field

extract(field from x)

format()

Format a datetime as a string

format(datetime as pattern)

trunc() ortruncate()

Datetime truncation

truncate(datetime, field)

Extracting date and time fields

The special functionextract() obtains a single field of a date, time, or datetime.

  • Its first argument is an expression that evaluates to a date, time, or datetime.

  • Its second argument is a date/timefield type.

Recognized Field types are listed below.

FieldTypeRangeNotesJPA standard

day

Integer

1-31

Calendar day of month

month

Integer

1-12

year

Integer

week

Integer

1-53

ISO-8601 week number (different toweek of year)

quarter

Integer

1-4

Quarter defined as 3 months

hour

Integer

0-23

Standard 24-hour time

minute

Integer

0-59

second

Float

0-59

Includes fractional seconds

nanosecond

Long

Granularity varies by database

day of week

Integer

1-7

day of month

Integer

1-31

Synonym forday

day of year

Integer

1-365

week of month

Integer

1-5

week of year

Integer

1-53

epoch

Long

Elapsed seconds since January 1, 1970

date

LocalDate

Date part of a datetime

time

LocalTime

Time part of a datetime

offset

ZoneOffset

Timezone offset

offset hour

Integer

Hours of offset

offset minute

Integer

0-59

Minutes of offset

For a full list of field types, see the Javadoc forTemporalUnit.

fromOrderwhereextract(datefromcreated)=localdate
selectextract(yearfromcreated),extract(monthfromcreated)fromOrder

The following functions are abbreviations forextract():

FunctionLong form usingextract()JPA standard

year(x)

extract(year from x)

month(x)

extract(month from x)

day(x)

extract(day from x)

hour(x)

extract(hour from x)

minute(x)

extract(minute from x)

second(x)

extract(second from x)

These abbreviations aren’t part of the JPQL standard, but on the other hand they’re a lot less verbose.
selectyear(created),month(created)fromOrder
Formatting dates and times

Theformat() function formats a date, time, or datetime according to a pattern.

  • Its first argument is an expression that evaluates to a date, time, or datetime.

  • Its second argument is a formatting pattern, given as a string.

The pattern must be written in a subset of the pattern language defined by Java’sjava.time.format.DateTimeFormatter.

selectformat(localdatetimeas'yyyy-MM-dd HH:mm:ss')

For a full list offormat() pattern elements, see the Javadoc forDialect.appendDatetimeFormat.

Truncating a date or time type

Thetruncate() function truncates the precision of a date, time, or datetime to the temporal unit specified by field type.

  • Its first argument is an expression that evaluates to a date, time, or datetime.

  • Its second argument is a date/time field type, specifying the precision of the truncated value.

Supported temporal units are:year,month,day,hour,minute orsecond.

selecttrunc(localdatetime,hour)

Truncating a date, time or datetime value means obtaining a value of the same type in which all temporal units smaller thanfield have been pruned.For hours, minutes and second this means setting them to00. For months and days, this means setting them to01.

2.6.4. Functions for working with strings

Naturally, there are a good number of functions for working with strings.

FunctionPurposeSyntaxJPA standard / ANSI SQL Standard

upper()

The string, with lowercase characters converted to uppercase

upper(str)

✔ / ✔

lower()

The string, with uppercase characters converted to lowercase

lower(str)

✔ / ✔

length()

The length of the string

length(str)

✔ / ✖

concat()

Concatenate strings

concat(x, y, z)

✔ / ✖

locate()

Location of string within a string

locate(patt, str),
locate(patt, str, start)

✔ / ✖

position()

Similar tolocate()

position(patt in str)

✖ / ✔

substring()

Substring of a string (JPQL-style)

substring(str, start),
substring(str, start, len)

✔ / ✖

substring()

Substring of a string (ANSI SQL-style)

substring(str from start),
substring(str from start for len)

✖ / ✔

trim()

Trim characters from string

trim(str),
trim(leading from str),
trim(trailing from str), or
trim(leading char from str)

✔ / ✔

overlay()

For replacing a substring

overlay(str placing rep from start),
overlay(str placing rep from start for len)

✖ / ✔

pad()

Pads a string with whitespace, or with a specified character

pad(str with len),
pad(str with len leading),
pad(str with len trailing), or
pad(str with len leading char)

✖ / ✖

left()

The leftmost characters of a string

left(str, len)

✖ / ✖

right()

The rightmost characters of a string

right(str, len)

✖ / ✖

replace()

Replace every occurrence of a pattern in a string

replace(str, patt, rep)

✖ / ✖

repeat()

Concatenate a string with itself multiple times

repeat(str, times)

✖ / ✖

collate()

Select a collation

collate(p.name as collation)

✖ / ✖

Let’s take a closer look at just some of these.

Contrary to Java, positions of characters within strings are indexed from 1 instead of 0!

Concatenating strings

The JPQL-standard and ANSI SQL-standardconcat() function accepts a variable number of arguments, and produces a string by concatenating them.

selectconcat(book.title,' by ',listagg(author.name,' & '))fromBookasbookjoinbook.authorsasauthorgroupbybook
Finding substrings

The JPQL functionlocate() determines the position of a substring within another string.

  • The first argument is the pattern to search for within the second string.

  • The second argument is the string to search in.

  • The optional third argument is used to specify a position at which to start the search.

selectlocate('Hibernate',title)fromBook

Theposition() function has a similar purpose, but follows the ANSI SQL syntax.

selectposition('Hibernate'intitle)fromBook
Slicing strings

Unsurprisingly,substring() returns a substring of the given string.

  • The second argument specifies the position of the first character of the substring.

  • The optional third argument specifies the maximum length of the substring.

selectsubstring(title,1,position(' for Dummies'intitle))fromBook/* JPQL-style */selectsubstring(titlefrom1forposition(' for Dummies'intitle))fromBook/* ANSI SQL-style */
Trimming strings

Thetrim() function follows the syntax and semantics of ANSI SQL.It may be used to trimleading characters,trailing characters, or both.

selecttrim(title)fromBook
selecttrim(trailing' 'fromtext)fromBook

Its BNF is funky:

"TRIM" "(" (("LEADING" | "TRAILING" | "BOTH")? trimCharacter? "FROM")? expression ")" ;
Padding strings

Thepad() function has a syntax inspired bytrim().

selectconcat(pad(b.titlewith40trailing'.'),pad(a.firstNamewith10leading),pad(a.lastNamewith10leading))fromBookasbjoinb.authorsasa

Its BNF is given by:

"PAD" "(" expression "WITH" expression ("LEADING" | "TRAILING") padCharacter? ")"
Collations

Thecollate() function selects a collation to be used for its string-valued argument.Collations are useful forbinary comparisons with< or>, and in theorder by clause.

For example,collate(p.name as ucs_basic) specifies the SQL standard collationucs_basic.

Collations aren’t very portable between databases.
Some PostgreSQL collation names must be quoted with backticks, for example,collate(name as `zh_TW.UTF-8`).
The@Collate annotation may be used to specify the collation of a column, which is usually more convenient than using thecollate() function.

2.6.5. Numeric functions

Of course, we also have a number of functions for working with numeric values.

FunctionPurposeSignatureJPA standard

abs()

The magnitude of a number

abs(x)

sign()

The sign of a number

sign(x)

mod()

Remainder of integer division

mod(n,d)

sqrt()

Square root of a number

sqrt(x)

exp()

Exponential function

exp(x)

power()

Exponentiation

power(x,y)

ln()

Natural logarithm

ln(x)

round()

Numeric rounding

round(number),
round(number, places)

trunc() ortruncate()

Numeric truncation

truncate(number),
truncate(number, places)

floor()

Floor function

floor(x)

ceiling()

Ceiling function

ceiling(x)

log10()

Base-10 logarithm

log10(x)

log()

Arbitrary-base logarithm

log(b,x)

pi

π

pi

sin(),cos(),tan(),asin(),acos(),atan()

Basic trigonometric functions

sin(theta),cos(theta)

atan2()

Two-argument arctangent (range(-π,π])

atan2(y, x)

sinh(),cosh(),tanh()

Hyperbolic functions

sinh(x),cosh(x),tanh(x)

degrees()

Convert radians to degrees

degrees(x)

radians()

Convert degrees to radians

radians(x)

least()

Return the smallest of the given arguments

least(x, y, z)

greatest()

Return the largest of the given arguments

greatest(x, y, z)

bitand(),bitor(),bitxor()

Bitwise functions

bitand(x,y)

We haven’t includedaggregate functions,ordered set aggregate functions, orwindow functions in this list, because their purpose is more specialized, and because they come with extra special syntax.

2.6.6. Functions for dealing with collections

The functions described in this section are especially useful when dealing with@ElementCollection mappings, or with collection mappings involving an@OrderColumn or@MapKeyColumn.

The following functions accept either:

  1. an identification variable that refers to ajoined collection or many-valued association, or

  2. acompound path that refers to a collection or many-valued association of an entity.

In case 2, application of the function produces animplicit join.

FunctionApplies toPurposeJPA standard

size()

Any collection

The size of a collection

element()

Any collection

The element of a set or list

index()

Lists

The index of a list element

key()

Maps

The key of a map entry

value()

Maps

The value of a map entry

entry() 💀

Maps

The whole entry in a map

The next group of functions always accept a compound path referring to a collection or many-valued association of an entity.They’re interpreted as referring to the collection as a whole.

FunctionApplies toPurposeJPA standard

elements()

Any collection

The elements of a set or list, collectively

indices()

Lists

The indexes of a list, collectively

keys()

Maps

The keys of a map, collectively

values()

Maps

The values of a map, collectively

Application of one of these function produces an implicit subquery or implicit join.

This query has an implicit join:

selecttitle,element(tags)fromBook

This query has an implicit subquery:

selecttitlefromBookwhere'hibernate'inelements(tags)
It never makes sense to apply the functionselements(),indices(),keys(), orvalues() to an identification variable or single-valued path expression.These functions must be applied to a reference to a many-valued path expression.
Collection sizes

Thesize() function returns the number of elements of a collection or to-many association.

selectname,size(books)fromAuthor
Set or list elements

Theelement() function returns a reference to an element of a joined set or list.For an identification variable (case 1 above), this function is optional.For a compound path (case 2), it’s required.

List indexes

Theindex() function returns a reference to the index of a joined list.

In this example,element() is optional, butindex() is required:

selectid(book),index(ed),element(ed)fromBookbookasbookjoinbook.editionsased
Map keys and values

Thekey() function returns a reference to a key of a joined map.Thevalue() function returns a reference to its value.

selectkey(entry),value(entry)fromThingasthingjointhing.entriesasentry
Quantification over collections

The functionselements(),indices(),keys(), andvalues() are used to quantify over collections.We may use them with:

ShortcutEquivalent subquery

exists elements(book.editions)

exists (select ed from book.editions as ed)

2 in indices(book.editions)

2 in (select index(ed) from book.editions as ed)

10 > all(elements(book.printings))

10 > all(select pr from book.printings as pr)

max(elements(book.printings))

(select max(pr) from book.printings as pr)

For example:

selecttitlefromBookwhere'hibernate'inelements(tags)

Don’t confuse theelements() function withelement(), theindices() function withindex(), thekeys() function withkey(), or thevalues() function withvalue().The functions named in singular deal with elements of "flattened" collections.If not already joined, they add an implicit join to the query.The functions with plural naming donot flatten a collection by joining it.

The following queries are different:

selecttitle,max(index(revisions))fromBook/* implicit join */
selecttitle,max(indices(revisions))fromBook/* implicit subquery */

The first query produces a single row, withmax() taken over all books.The second query produces a row per book, withmax() taken over the collection elements belonging to the given book.

2.6.7. Functions for working with ids and versions

Finally, the following functions evaluate the id, version, or natural id of an entity, or the foreign key of a to-one association:

FunctionPurposeJPA standard

id()

The value of the entity@Id attribute.

version()

The value of the entity@Version attribute.

naturalid()

The value of the entity@NaturalId attribute.

fk()

The value of the foreign key column mapped by a@ManyToOne (or logical@OneToOne) association.Useful with associations annotated@NotFound.

2.6.8. Embedding SQL expressions

The following special functions let us embed a call to a native SQL function, refer directly to a column, or evaluate an expression written in native SQL.

FunctionPurposeSignatureJPA standard

function()

Call a SQL function

function('fun', arg1, arg2)

function()

Call a SQL function

function(fun, arg1, arg2),
function(fun as Type, arg1, arg2)

column()

A column value

column(entity.column),
column(entity.column as Type)

sql()

Evaluate a SQL expression

sql('text', arg1, arg2)

Before using one of these functions, ask yourself if it might be better to just write the whole query in native SQL.
Direct column references

Thecolumn() function lets us refer to an unmapped column of a table.The column name must be qualified by an identification variable or path expression.

selectcolumn(log.ctidasString)fromLoglog

Of course, the table itself must be mapped by an entity class.

Native and user-defined functions

The functions we’ve described above are the functions abstracted by HQL and made portable across databases.But, of course, HQL can’t abstract every function in your database.

There are several ways to call native or user-defined SQL functions.

  • A native or user-defined function may be called using JPQL’sfunction syntax, for example,function('sinh', phi), or HQL’s extension to that syntax, for examplefunction(sinh as Double, phi).(This is the easiest way, but not the best way.)

  • A user-writtenFunctionContributor may register user-defined functions.

  • A customDialect may register additional native functions by overridinginitializeFunctionRegistry().

Registering a function isn’t hard, but is beyond the scope of this guide.

(It’s even possible to use the APIs Hibernate provides to make your ownportable functions!)

Fortunately, every built-inDialect already registers many native functions for the database it supports.

Try setting the log categoryorg.hibernate.HQL_FUNCTIONS to debug.Then at startup Hibernate will log a list of type signatures of all registered functions.

Embedding native SQL in HQL

The special functionsql() allows the use of native SQL fragments inside an HQL query.

The signature of this function issql(pattern[, argN]*), wherepattern must be a string literal but the remaining arguments may be of any type.The pattern literal is unquoted and embedded in the generated SQL.Occurrences of? in the pattern are replaced with the remaining arguments of the function.

We may use this, for example, to perform a native PostgreSQL typecast:

fromComputercwherec.ipAddress=sql('?::inet','127.0.0.1')

This results in SQL logically equivalent to:

select*fromComputercwherec.ipAddress='127.0.0.1'::inet

Or we can use a native SQL operator:

fromHumanhorderbysql('(? <-> ?)',h.workLocation,h.homeLocation)

And this time the SQL is logically equivalent to:

select*fromHumanhwhere(h.workLocation<->h.homeLocation)

2.7. Predicates

A predicate is an operator which, when applied to some argument, evaluates totrue orfalse.In the world of SQL-style ternary logic, we must expand this definition to encompass the possibility that the predicate evaluates tonull.Typically, a predicate evaluates tonull when one of its arguments isnull.

Predicates occur in thewhere clause, thehaving clause and in searched case expressions.

2.7.1. Comparison operators

The binary comparison operators are borrowed from SQL:=,>,>=,<,<=,<>.

If you prefer, HQL treats!= as a synonym for<>.

The operands should be of the same type.

fromBookwhereprice<1.0
fromAuthorasauthorwhereauthor.nomDePlume<>author.person.name
selectid,totalfrom(selectord.idasid,sum(item.book.price*item.quantity)astotalfromOrderasordjoinItemasitemgroupbyord)wheretotal>100.0

2.7.2. Thebetween predicate

The ternarybetween operator, and its negation,not between, determine if a value falls within a range.

Of course, all three operands must be of compatible type.

fromBookwherepricebetween1.0and100.0

2.7.3. Operators for dealing with null

The following operators make it easier to deal with null values.These predicates never evaluate tonull.

OperatorNegationTypeSemantics

is null

is not null

Unary postfix

true if the value to the left is null, or false if it is not null

is distinct from

is not distinct from

Binary

true if the value on the left is equal to the value on the right, or if both values are null, and false otherwise

fromAuthorwherenomDePlumeisnotnull

2.7.4. Operators for dealing with boolean values

These operators perform comparisons on values of typeboolean.These predicates never evaluate tonull.

The valuestrue andfalse of theboolean basic type are different to the logicaltrue orfalse produced by a predicate.

Forlogical operations onpredicates, seeLogical operators below.

OperatorNegationTypeSemantics

is true

is not true

Unary postfix

true if the value to the left istrue, orfalse otherwise

is false

is not false

Binary

true if the value to the left isfalse, orfalse otherwise

fromBookwherediscontinuedisnottrue

2.7.5. Collection predicates

The following operators apply to collection-valued attributes and to-many associations.

OperatorNegationTypeSemantics

is empty

is not empty

Unary postfix

true if the collection or association on the left has no elements

member of

not member of

Binary

true if the value on the left is a member of the collection or association on the right

fromAuthorwherebooksisempty
selectauthor,bookfromAuthorasauthor,Bookasbookwhereauthormemberofbook.authors

2.7.6. String pattern matching

Thelike operator performs pattern matching on strings.Its friendilike performs case-insensitive matching.

Their syntax is defined by:

expression "NOT"? ("LIKE" | "ILIKE") expression ("ESCAPE" character)?

The expression on the right is a pattern, where:

  • _ matches any single character,

  • % matches any number of characters, and

  • if an escape character is specified, it may be used to escape either of these wildcards.

fromBookwheretitlenotlike'% for Dummies'

The optionalescape character allows a pattern to include a literal_ or% character.

As you can guess,not like andnot ilike are the enemies oflike andilike, and evaluate to the exact opposite boolean values.

2.7.7. Thein predicate

Thein predicates evaluates to true if the value to its left is in …​ well, whatever it finds to its right.

Its syntax is unexpectedly complicated:

expression "NOT"? "IN" inListinList: collectionQuantifier "(" simplePath ")"| "(" (expression ("," expression)*)? ")"| "(" subquery ")"| parameter

This less-than-lovely fragment of the HQL ANTLR grammar tells us that the thing to the right might be:

  • a list of values enclosed in parentheses,

  • a subquery,

  • one of the collection-handling functions definedabove, or

  • a query parameter,

The type of the expression on the left, and the types of all the values on the right must be compatible.

JPQL limits the legal types to string, numeric, date/time, and enum types, and in JPQL the left expression must be either:

  • astate field, which means a basic attribute, excluding associations and embedded attributes, or

  • anentity type expression.

HQL is far more permissive. HQL itself does not restrict the type in any way, though the database itself might.Even embedded attributes are allowed, although that feature depends on the level of support for tuple or "row value" constructors in the underlying database.

fromPaymentaspaymentwheretype(payment)in(CreditCardPayment,WireTransferPayment)
fromAuthorasauthorwhereauthor.person.namein(selectnamefromOldAuthorData)
fromBookasbookwhere:editioninelements(book.editions)

It’s quite common to have a parameterized list of values.

Here’s a very useful idiom:

List<Book>books=session.createSelectionQuery("from Book where isbn in :isbns",Book.class).setParameterList("isbns",listOfIsbns).getResultList();

We may even "vectorize" anin predicate, using a tuple constructor and a subquery with multiple selection items:

fromAuthorasauthorwhere(author.person.name,author.person.birthdate)in(selectname,birthdatefromOldAuthorData)

2.7.8. Comparison operators and subqueries

The binary comparisons we metabove may involve a quantifier, either:

  • a quantified subquery, or

  • a quantifier applied to one of the functions definedabove.

The quantifiers are unary prefix operators:all,every,any, andsome.

Subquery operatorSynonymSemantics

every

all

Evaluates to true of the comparison is true forevery value in the result set of the subquery

any

some

Evaluates to true of the comparison is true forat least one value in the result set of the subquery

fromPublisherpubwhere100.0<all(selectpricefrompub.books)
fromPublisherpubwhere:title=some(selecttitlefrompub.books)

2.7.9. Theexists predicate

The unary prefixexists operator evaluates to true if the thing to its right is nonempty.

The thing to its right might be:

  • a subquery, or

  • one of the functions definedabove.

As you can surely guess,not exists evaluates to true if the thing to the rightis empty.

fromAuthorwhereexistselements(books)
fromAuthorasauthorwhereexists(fromOrderjoinitemswherebookinelements(author.books))

2.7.10. Logical operators

The logical operators are binary infixand andor, and unary prefixnot.

Just like SQL, logical expressions are based on ternary logic.A logical operator evaluates to null if it has a null operand.

3. Root entities and joins

Thefrom clause, and its subordinatejoin clauses sit right at the heart of most queries.

3.1. Declaring root entities

Thefrom clause is responsible for declaring the entities available in the rest of the query, and assigning them aliases, or, in the language of the JPQL specification,identification variables.

3.1.1. Identification variables

An identification variable is just a name we can use to refer to an entity and its attributes from expressions in the query.It may be any legal Java identifier.According to the JPQL specification, identification variables must be treated as case-insensitive language elements.

The identification variable is actually optional, but for queries involving more than one entity it’s almost always a good idea to declare one.

Thisworks, but it isn’t particularly good form:

fromPublisherjoinbooksjoinauthorsjoinpersonwheressn=:ssn

Identification variables may be declared with theas keyword, but this is optional.

3.1.2. Root entity references

A root entity reference, or what the JPQL specification calls arange variable declaration, is a direct reference to a mapped@Entity type by its entity name.

Remember, theentity name is the value of thename member of the@Entity annotation, or the unqualified Java class name by default.

selectbookfromBookasbook

In this example,Book is the entity name, andbook is the identification variable.Theas keyword is optional.

Alternatively, a fully-qualified Java class name may be specified.Then Hibernate will query every entity which inherits the named type.

selectdocfromorg.hibernate.example.AbstractDocumentasdocwheredoc.textlike:pattern

Of course, there may be multiple root entities.

selecta,bfromAuthora,Authorb,Bookbookwhereainelements(book.authors)andbinelements(book.authors)

This query may even be written using the syntaxcross join in place of the commas:

selecta,bfromBookbookcrossjoinAuthoracrossjoinAuthorbwhereainelements(book.authors)andbinelements(book.authors)

Of course, it’s possible to write old-fashioned pre-ANSI-era joins:

selectbook.title,publisher.namefromBookbook,Publisherpublisherwherebook.publisher=publisherandbook.titlelike:titlePattern

But we never write HQL this way.

3.1.3. Polymorphism

HQL and JPQL queries are inherently polymorphic.Consider:

selectpaymentfromPaymentaspayment

This query names thePayment entity explicitly.But theCreditCardPayment andWireTransferPayment entities inheritPayment, and sopayment ranges over all three types.Instances of all these entities are returned by the query.

The queryfrom java.lang.Object is completely legal. (But not very useful!)

It returns every object of every mapped entity type.

3.1.4. Derived roots

Aderived root is an uncorrelated subquery which occurs in thefrom clause.

selectid,totalfrom(selectord.idasid,sum(item.book.price*item.quantity)astotalfromOrderasordjoinItemasitemgroupbyord)wheretotal>100.0

The derived root may declare an identification variable.

selectstuff.id,stuff.totalfrom(selectord.idasid,sum(item.book.price*item.quantity)astotalfromOrderasordjoinItemasitemgroupbyord)asstuffwheretotal>100.0

This feature can be used to break a more complicated query into smaller pieces.

We emphasize that a derived root must be anuncorrelated subquery.It may not refer to other roots declared in the samefrom clause.

A subquery may also occur in ajoin, in which case it may be a correlated subquery.

3.1.5. Common table expressions infrom clause

Acommon table expression (CTE) is like a derived root with a name.We’ll discuss CTEslater.

3.2. Declaring joined entities

Joins allow us to navigate from one entity to another, via its associations, or via explicit join conditions.There are:

  • explicit joins, declared within thefrom clause using the keywordjoin, and

  • implicit joins, which don’t need to be declared in thefrom clause.

An explicit join may be either:

  • aninner join, written asjoin orinner join,

  • aleft outer join, written asleft join orleft outer join,

  • aright outer join, written asright join orright outer join, or

  • afull outer join, written asfull join orfull outer join.

3.2.1. Explicit root joins

An explicit root join works just like an ANSI-style join in SQL.

selectbook.title,publisher.namefromBookbookjoinPublisherpublisheronbook.publisher=publisherwherebook.titlelike:titlePattern

The join condition is written out explicitly in theon clause.

This looks nice and familiar, but it’snot the most common sort of join in HQL or JPQL.

3.2.2. Explicit association joins

Every explicit association join specifies an entity attribute to be joined.The specified attribute:

  • is usually a@OneToMany,@ManyToMany,@OneToOne, or@ManyToOne association, but

  • it could be an@ElementCollection, and

  • it might even be an attribute of embeddable type.

In the case of an association or collection, the generated SQL will have a join of the same type.(For a many-to-many association it will havetwo joins.)In the case of an embedded attribute, the join is purely logical and does not result in a join in the generated SQL.

An explicit join may assign an identification variable to the joined entity.

fromBookasbookjoinbook.publisheraspublisherjoinbook.authorsasauthorwherebook.titlelike:titlePatternselectbook.title,author.name,publisher.name

For an outer join, we must write our query to accommodate the possibility that the joined association is missing.

fromBookasbookleftjoinbook.publisheraspublisherjoinbook.authorsasauthorwherebook.titlelike:titlePatternselectbook.title,author.name,ifnull(publisher.name,'-')

For further information about collection-valued association references, seeJoining collections and many-valued associations.

3.2.3. Explicit association joins with join conditions

Thewith oron clause allows explicit qualification of the join conditions.

The specified join conditions areadded to the join conditions specified by the foreign key association.That’s why, historically, HQL uses the kewordwith here:"with" emphasizes that the new condition doesn’treplace the original join conditions.

Thewith keyword is specific to Hibernate. JPQL useson.

Join conditions occurring in thewith oron clause are added to theon clause in the generated SQL.

fromBookasbookleftjoinbook.publisheraspublisherwithpublisher.closureDateisnotnullleftjoinbook.authorsasauthorwithauthor.type<>COLLABORATIONwherebook.titlelike:titlePatternselectbook.title,author.name,publisher.name

3.2.4. Association fetching

Afetch join overrides the laziness of a given association, specifying that the association should be fetched with a SQL join.The join may be an inner or outer join.

  • Ajoin fetch, or, more explicitly,inner join fetch, only returns base entities with an associated entity.

  • Aleft join fetch, or—for lovers of verbosity—left outer join fetch, returns all the base entities, including those which have no associated joined entity.

This is one of the most important features of Hibernate.To achieve acceptable performance with HQL, you’ll need to usejoin fetch quite often.Without it, you’ll quickly run into the dreaded "n+1 selects" problem.

For example, ifPerson has a one-to-many association namedphones, the use ofjoin fetch in the following query specifies that the collection elements should be fetched in the same SQL query:

selectbookfromBookasbookleftjoinfetchbook.publisherjoinfetchbook.authors

In this example, we used a left outer join forbook.publisher because we also wanted to obtain books with no publisher, but a regular inner join forbook.authors because every book has at least one author.

A query may have more than one fetch join, but be aware that:

  • it’s perfectly safe to fetch several to-one associations in series or parallel in a single query, and

  • a single series ofnested fetch joins is also fine, but

  • fetching multiple collections or to-many associations inparallel results in a Cartesian product at the database level, and might exhibit very poor performance.

HQL doesn’t disallow it, but it’s usually a bad idea to apply a restriction to ajoin fetched entity, since the elements of the fetched collection would be incomplete.Indeed, it’s best to avoid even assigning an identification variable to a fetched joined entity except for the purpose of specifying a nested fetch join.

Fetch joins should usually be avoided in limited or paged queries.This includes:

  • queries executed with limits specified via thesetFirstResult() andsetMaxResults() methods ofQuery, or

  • queries with a limit or offset declared in HQL, described below inLimits and offsets.

Nor should they be used with thescroll() andstream() methods of theQuery interface.

Fetch joins are disallowed in subqueries, where they would make no sense.

3.2.5. Joins with typecasts

An explicit join may narrow the type of the joined entity usingtreat().

fromOrderasordjointreat(ord.paymentsasCreditCardPayment)ascreditCardPaymentwherelength(creditCardPayment.cardNumber)between16and20selectord.id,creditCardPayment.cardNumber,creditCardPayment.amount

Here, the identification variableccp declared to the right oftreat() has the narrowed typeCreditCardPayment, instead of the declared typePayment.This allows the attributecardNumber declared by the subtypeCreditCardPayment to be referenced in the rest of the query.

SeeTypes and typecasts for more information abouttreat().

3.2.6. Subqueries in joins

Ajoin clause may contain a subquery, either:

  • an uncorrelated subquery, which is almost the same as aderived root, except that it may have anon restriction, or

  • alateral join, which is a correlated subquery, and may refer to other roots declared earlier in the samefrom clause.

Thelateral keyword just distinguishes the two cases.

fromPhoneasphoneleftjoin(selectcall.durationasduration,call.phone.idascidfromCallascallorderbycall.durationdesclimit1)aslongestoncid=phone.idwherephone.number=:phoneNumberselectlongest.duration

This query may also be expressed using alateral join:

fromPhoneasphoneleftjoinlateral(selectcall.durationasdurationfromphone.callsascallorderbycall.durationdesclimit1)aslongestwherephone.number=:phoneNumberselectlongest.duration

A lateral join may be an inner or left outer join, but not a right join, nor a full join.

Traditional SQL doesn’t allow correlated subqueries in thefrom clause.A lateral join is essentially just that, but with a different syntax to what you might expect.

On some databases,join lateral is writtencross apply.And on Postgres it’s plainlateral, withoutjoin.

It’s almost as if they’redeliberately trying to confuse us.

Lateral joins are particularly useful for computing top-N elements of multiple groups.

Most databases support some flavor ofjoin lateral, and Hibernate emulates the feature for databases which don’t.But emulation is neither very efficient, nor does it support all possible query shapes, so it’s important to test on your target database.

3.2.7. Implicit association joins (path expressions)

It’s not necessary to explicitlyjoin every entity that occurs in a query.Instead, entity associations may benavigated, just like in Java:

  • if an attribute is of embedded type, or is a to-one association, it may be further navigated, but

  • if an attribute is of basic type, it is considered terminal, and may not be further navigated, and

  • if an attribute is collection-valued, or is a to-many association, it may be navigated, but only with the help ofvalue(),element(), orkey().

It’s clear that:

  • A path expression likeauthor.name with only two elements just refers to state held directly by an entity with an aliasauthor defined infrom orjoin.

  • But a longer path expression, for example,author.person.name, might refer to state held by an associated entity.(Alternatively, it might refer to state held by an embedded class.)

In the second case, Hibernate with automatically add a join to the generated SQL if necessary.

fromBookasbookwherebook.publisher.namelike:pubName

As in this example, implicit joins usually appear outside thefrom clause of the HQL query.However, they always affect thefrom clause of the SQL query.

The example above is equivalent to:

selectbookfromBookasbookjoinbook.publisheraspubwherepub.namelike:pubName

Note that:

  • Implicit joins are always treated as inner joins.

  • Multiple occurrences of the same implicit join always refer to the same SQL join.

This query:

selectbookfromBookasbookwherebook.publisher.namelike:pubNameandbook.publisher.closureDateisnull

results in just one SQL join, and is just a different way to write:

selectbookfromBookasbookjoinbook.publisheraspubwherepub.namelike:pubNameandpub.closureDateisnull

3.2.8. Joining collections and many-valued associations

When a join involves a collection or many-valued association, the declared identification variable refers to theelements of the collection, that is:

  • to the elements of aSet,

  • to the elements of aList, not to their indices in the list, or

  • to the values of aMap, not to their keys.

selectpublisher.name,author.namefromPublisheraspublisherjoinpublisher.booksasbookjoinbook.authorsauthorwhereauthor.namelike:namePattern

In this example, the identification variableauthor is of typeAuthor, the element type of the listBook.authors.But if we need to refer to the index of anAuthor in the list, we need some extra syntax.

You might recall that we mentionedList indexes andMap keys and values a bit earlier.These functions may be applied to the identification variable declared in a collection join or many-valued association join.

FunctionApplies toInterpretationNotes

value() orelement()

Any collection

The collection element or map entry value

Often optional.

index()

AnyList with an index column

The index of the element in the list

For backward compatibility, it’s also an alternative tokey(), when applied to a map.

key()

AnyMap

The key of the entry in the list

If the key is of entity type, it may be further navigated.

entry()

AnyMap

The map entry, that is, theMap.Entry of key and value.

Only legal as a terminal path, and only allowed in theselect clause.

In particular,index() andkey() obtain a reference to a list index or map key.

selectbook.title,author.name,index(author)fromBookasbookjoinbook.authorsasauthor
selectpublisher.name,leadAuthor.namefromPublisheraspublisherjoinpublisher.booksasbookjoinbook.authorsleadAuthorwhereleadAuthor.namelike:namePatternandindex(leadAuthor)==0

3.2.9. Implicit joins involving collections

A path expression likebook.authors.name is not considered legal.We can’t just navigate a many-valued association with this syntax.

Instead, the functionselement(),index(),key(), andvalue() may be applied to a path expression to express an implicit join.So we must writeelement(book.authors).name orindex(book.authors).

selectbook.title,element(book.authors).name,index(book.authors)fromBookbook

An element of an indexed collection (an array, list, or map) may even be identified using the index operator:

selectpublisher.name,book.authors[0].namefromPublisheraspublisherjoinpublisher.booksasbookwherebook.authors[0].namelike:namePattern

4. Selection, projection, and aggregation

Joining is one kind ofrelational operation.It’s an operation that produces relations (tables) from other relations.Such operations, taken together, form therelational algebra.

We must now understand the rest of this family: restriction a.k.a. selection, projection, aggregation, union/intersection, and, finally, ordering and limiting, operations which are not strictly part of the calculus of relations, but which usually come along for the ride because they’re veryuseful.

We’ll start with the operation that’s easiest to understand.

4.1. Restriction

Thewhere clause restricts the results returned by aselect query or limits the scope of anupdate ordelete query.

This operation is usually calledselection, but since that term is often confused with theselect keyword, and since both projection and selection involve "selecting" things, here we’ll use the less-ambiguous termrestriction.

A restriction is nothing more than a single logical expression, a topic we exhausted above inPredicates.Therefore, we’ll move quickly onto the next, and more interesting, operation.

4.2. Aggregation

An aggregate query is one withaggregate functions in its projection list.It collapses multiple rows into a single row.Aggregate queries are used for summarizing and analysing data.

An aggregate query might have agroup by clause.Thegroup by clause divides the result set into groups, so that a query with aggregate functions in the select list returns not a single result for the whole query, but one result for each group.If an aggregate querydoesn’t have agroup by clause, it always produces a single row of results.

In short,grouping controls the effect ofaggregation.

A query with aggregation may also have ahaving clause, a restriction applied to the groups.

4.2.1. Aggregation and grouping

Thegroup by clause looks quite similar to theselect clause—it has a list of grouped items, but:

  • if there’s just one item, then the query will have a single result for each unique value of that item, or

  • if there are multiple items, the query will have a result for each uniquecombination or their values.

The BNF for a grouped item is just:

identifier | INTEGER_LITERAL | expression

Consider the following queries:

selectbook.isbn,sum(quantity)astotalSold,sum(quantity*book.price)astotalBilledfromItemwherebook.isbn=:isbn
selectbook.isbn,year(order.dateTime)asyear,sum(quantity)asyearlyTotalSold,sum(quantity*book.price)asyearlyTotalBilledfromItemwherebook.isbn=:isbngroupbyyear(order.dateTime)

The first query calculates complete totals over all orders in years.The second calculates totals for each year, after grouping the orders by year.

4.2.2. Totals and subtotals

The special functionsrollup() andcube() may be used in thegroup by clause, when supported by the database.The semantics are identical to SQL.

These functions are especially useful for reporting.

  • Agroup by clause withrollup() is used to produce subtotals and grand totals.

  • Agroup by clause withcube() allows totals for every combination of columns.

4.2.3. Aggregation and restriction

In a grouped query, thewhere clause applies to the non-aggregated values (it determines which rows will make it into the aggregation).Thehaving clause also restricts results, but it operates on the aggregated values.

In anexample above, we calculated totals for every year for which data was available.But our dataset might extend far back into the past, perhaps even as far back as those terrible dark ages before Hibernate 2.0.So let’s restrict our result set to data from our own more civilized times:

selectbook.isbn,year(order.dateTime)asyear,sum(quantity)asyearlyTotalSold,sum(quantity*book.price)asyearlyTotalBilledfromItemwherebook.isbn=:isbngroupbyyear(order.dateTime)havingyear(order.dateTime)>2003andsum(quantity)>0

Thehaving clause follows the same rules as thewhere clause and is also just a logical predicate.Thehaving restriction is applied after grouping and aggregation has already been performed, whereas thewhere clause is applied before the data is grouped or aggregated.

4.3. Projection

Theselect list identifies which objects and values to return as the query results.This operation is calledprojection.

selectClause    : "SELECT" "DISTINCT"? selection (","" selection)*

Any of the expression types discussed inExpressions may occur in the projection list, unless otherwise noted.

If a query has no explicitselect list, then, as we sawmuch earlier, the projection is inferred from the entities and joins occurring in thefrom clause, together with the result type specified by the call tocreateQuery().But it’s better to specify the projection explicitly, except in the simplest cases.

4.3.1. Duplicate removal

Thedistinct keyword helps remove duplicate results from the query result list.It’s only effect is to adddistinct to the generated SQL.

selectdistinctlastNamefromPerson
selectdistinctauthorfromPublisheraspubjoinpub.booksasbookjoinbook.authorsasauthorwherepub.id=:pid

As of Hibernate 6, duplicate results arising from the use ofjoin fetch are automatically removed by Hibernate in memory,after reading the database results and materializing entity instances as Java objects.It’s no longer necessary to remove duplicate results explicitly, and, in particular,distinct should not be used for this purpose.

4.3.2. Aggregate functions

It’s common to have aggregate functions likecount(),sum(), andmax() in a select list.Aggregate functions are special functions that reduce the size of the result set.

The standard aggregate functions defined in both ANSI SQL and JPQL are these ones:

Aggregate functionArgument typeResult typeJPA standard / ANSI SQL standard

count(), includingcount(distinct),count(all), andcount(*)

Any

Long

✔/✔

avg()

Any numeric type

Double

✔/✔

min()

Any numeric type, or string

Same as the argument type

✔/✔

max()

Any numeric type, or string

Same as the argument type

✔/✔

sum()

Any numeric type

See table below

✔/✔

var_pop(),var_samp()

Any numeric type

Double

✖/✔

stddev_pop(),stddev_samp()

Any numeric type

Double

✖/✔

selectcount(distinctitem.book)fromItemasitemwhereyear(item.order.dateTime)=:year
selectsum(item.quantity)astotalSalesfromItemasitemwhereitem.book.isbn=:isbn
selectyear(item.order.dateTime)asyear,sum(item.quantity)asyearlyTotalfromItemasitemwhereitem.book.isbn=:isbngroupbyyear(item.order.dateTime)
selectmonth(item.order.dateTime)asmonth,avg(item.quantity)asmonthlyAveragefromItemasitemwhereitem.book.isbn=:isbngroupbymonth(item.order.dateTime)

In the case ofsum(), the rules for assigning a result type are:

Argument typeResult type

Any integral numeric type exceptBigInteger

Long

Any floating point numeric type

Double

BigInteger

BigInteger

BigDecimal

BigDecimal

HQL defines two additional aggregate functions which accept a logical predicate as an argument.

Aggregate functionArgument typeResult typeJPA standard

any() orsome()

Logical predicate

Boolean

every() orall()

Logical predicate

Boolean

We may write, for example,every(p.amount < 1000.0).

Below, we’ll meet theordered set aggregate functions.

Aggregate functions usually appear in theselect clause, but control over aggregation is the responsibility of thegroup by clause, as describedbelow.

4.3.3. Aggregate functions and collections

Theelements() andindices() functions we metearlier let us apply aggregate functions to a collection:

New syntaxLegacy HQL function 💀Applies toPurpose

max(elements(x))

maxelement(x)

Any collection with sortable elements

The maximum element or map value

min(elements(x))

minelement(x)

Any collection with sortable elements

The minimum element or map value

sum(elements(x))

Any collection with numeric elements

The sum of the elements or map values

avg(elements(x))

Any collection with numeric elements

The average of the elements or map values

max(indices(x))

maxindex(x)

Indexed collections (lists and maps)

The maximum list index or map key

min(indices(x))

minindex(x)

Indexed collections (lists and maps)

The minimum list index or map key

sum(indices(x))

Indexed collections (lists and maps)

The sum of the list indexes or map keys

avg(indices(x))

Indexed collections (lists and maps)

The average of the list indexes or map keys

These operations are mostly useful when working with@ElementCollections.

selecttitle,max(indices(authors))+1,max(elements(editions))fromBook

4.3.4. Aggregate functions with restriction

All aggregate functions support the inclusion of afilter clause, a sort of mini-where applying a restriction to just one item of the select list:

selectyear(item.order.dateTime)asyear,sum(item.quantity)filter(wherenotitem.order.fulfilled)asunfulfilled,sum(item.quantity)filter(whereitem.order.fulfilled)asfulfilled,sum(item.quantity*item.book.price)filter(whereitem.order.paid)fromItemasitemwhereitem.book.isbn=:isbngroupbyyear(item.order.dateTime)

The BNF for thefilter clause is simple:

filterClause: "FILTER" "(" "WHERE" predicate ")"

4.3.5. Ordered set aggregate functions

Anordered set aggregate function is a special aggregate function which has:

  • not only an optional filter clause, as above, but also

  • awithin group clause containing a mini-order by specification.

The BNF forwithin group is straightforward:

withinGroupClause: "WITHIN" "GROUP" "(" "ORDER" "BY" sortSpecification ("," sortSpecification)* ")"

There are two main types of ordered set aggregate function:

  • aninverse distribution function calculates a value that characterizes the distribution of values within the group, for example,percentile_cont(0.5) is the median, andpercentile_cont(0.25) is the lower quartile.

  • ahypothetical set function determines the position of a "hypothetical" value within the ordered set of values.

The following ordered set aggregate functions are available on many platforms:

TypeFunctions

Inverse distribution functions

mode(),percentile_cont(),percentile_disc()

Hypothetical set functions

rank(),dense_rank(),percent_rank(),cume_dist()

Other

listagg()

This query calculates the median price of a book:

selectpercentile_cont(0.5)withingroup(orderbyprice)fromBook

This query finds the percentage of books with prices less than 10 dollars:

select100*percent_rank(10.0)withingroup(orderbyprice)fromBook

Actually, the most widely-supported ordered set aggregate function is one which builds a string by concatenating the values within a group.This function has different names on different databases, but HQL abstracts these differences, and—following ANSI SQL—calls itlistagg().

selectlistagg(title,', ')withingroup(orderbyisbn)fromBookgroupbyelement(authors)

This very useful function produces a string by concatenation of the aggregated values of its argument.

4.3.6. Window functions

Awindow function is one which also has anover clause, for example:

selectitem.order.dateTime,sum(item.quantity)over(orderbyitem.order.dateTime)asrunningTotalfromItemitem

This query returns a running total of sales over time.That is, thesum() is taken over a window comprising the current row of the result set, together with all previous rows.

A window function application may optionally specify any of the following clauses:

Optional clauseKeywordPurpose

Partitioning of the result set

partition by

Very similar togroup by, but doesn’t collapse each partition to a single row

Ordering of the partition

order by

Specifies the order of rows within a partition

Windowing

range,rows, orgroups

Defines the bounds of a window frame within a partition

Restriction

filter

As aggregate functions, window functions may optionally specify a filter

For example, we may partition the running total by book:

selectitem.book.isbn,item.order.dateTime,sum(item.quantity)over(partitionbyitem.bookorderbyitem.order.dateTime)asrunningTotalfromItemitem

Every partition runs in isolation, that is, rows can’t leak across partitions.

The full syntax for window function application is amazingly involved, as shown by this BNF:

overClause: "OVER" "(" partitionClause? orderByClause? frameClause? ")"partitionClause: "PARTITION" "BY" expression ("," expression)*frameClause: ("RANGE"|"ROWS"|"GROUPS") frameStart frameExclusion?| ("RANGE"|"ROWS"|"GROUPS") "BETWEEN" frameStart "AND" frameEnd frameExclusion?frameStart: "CURRENT" "ROW"| "UNBOUNDED" "PRECEDING"| expression "PRECEDING"| expression "FOLLOWING"frameEnd: "CURRENT" "ROW"| "UNBOUNDED" "FOLLOWING"| expression "PRECEDING"| expression "FOLLOWING"frameExclusion: "EXCLUDE" "CURRENT" "ROW"| "EXCLUDE" "GROUP"| "EXCLUDE" "TIES"| "EXCLUDE" "NO" "OTHERS"

Window functions are similar to aggregate functions in the sense that they compute some value based on a "frame" comprising multiple rows.But unlike aggregate functions, window functions don’t flatten rows within a window frame.

Window frames

Thewindow frame is the set of rows within a given partition that is passed to the window function.There’s a different window frame for each row of the result set.In our example, the window frame comprised all the preceding rows within the partition, that is, all the rows with the sameitem.book and with an earlieritem.order.dateTime.

The boundary of the window frame is controlled via the windowing clause, which may specify one of the following modes:

ModeDefinitionExampleInterpretation

rows

Frame bounds defined by a given number of rows

rows 5 preceding

The previous 5 rows in the partition

groups

Frame bounds defined by a given number ofpeer groups, rows belonging to the same peer group if they are assigned the same position byorder by

groups 5 preceding

The rows in the previous 5 peer groups in the partition

range

Frame bounds defined by a maximum difference invalue of the expression used toorder by

range between 1.0 preceding and 1.0 following

The rows whoseorder by expression differs by a maximum absolute value of1.0 from the current row

The frame exclusion clause allows excluding rows around the current row:

OptionInterpretation

exclude current row

Excludes the current row

exclude group

Excludes rows of the peer group of the current row

exclude ties

Excludes rows of the peer group of the current row except the current row

exclude no others

The default, does not exclude anything

By default, the window frame is defined asrows between unbounded preceding and current row exclude no others, meaning every row up to and including the current row.

The modesrange andgroups, along with frame exclusion modes, are not available on every database.

Widely supported window functions

The following window functions are available on all major platforms:

Window functionPurposeSignature

row_number()

The position of the current row within its frame

row_number()

lead()

The value of a subsequent row in the frame

lead(x),lead(x, i, x)

lag()

The value of a previous row in the frame

lag(x),lag(x, i, x)

first_value()

The value of a first row in the frame

first_value(x)

last_value()

The value of a last row in the frame

last_value(x)

nth_value()

The value of the `n`th row in the frame

nth_value(x, n)

In principle every aggregate or ordered set aggregate function might also be used as a window function, just by specifyingover, but not every function is supported on every database.

Window functions and ordered set aggregate functions aren’t available on every database.Even where they are available, support for particular features varies widely between databases.Therefore, we won’t waste time going into further detail here.For more information about the syntax and semantics of these functions, consult the documentation for your dialect of SQL.

4.4. Operations on result sets

These operators apply not to expressions, but to entire result sets:

  • union andunion all,

  • intersect andintersect all, and

  • except andexcept all.

Just like in SQL,all suppresses the elimination of duplicate results.

selectnomDePlumefromAuthorwherenomDePlumeisnotnullunionselectnamefromPerson

4.5. Sorting

By default, the results of the query are returned in an arbitrary order.

Imposing an order on a set is calledsorting.

A relation (a database table) is a set, and therefore certain particularly dogmatic purists have argued that sorting has no place in the algebra of relations.We think this is more than a bit silly: practical data analysis almost always involves sorting, which is a perfectly well-defined operation.

Theorder by clause specifies a list of projected items used to sort the results.Each sorted item may be:

  • an attribute of an entity or embeddable class,

  • a more complexexpression,

  • the alias of a projected item declared in the select list, or

  • a literal integer indicating the ordinal position of a projected item in the select list.

Of course, in principle, only certain types may be sorted: numeric types, string, and date and time types.But HQL is very permissive here and will allow an expression of almost any type to occur in a sort list.Even the identification variable of an entity with a sortable identifier type may occur as a sorted item.

The JPQL specification requires that every sorted item in theorder by clause also occur in theselect clause.HQL does not enforce this restriction, but applications desiring database portability should be aware that some databasesdo.

Therefore, you might wish to avoid the use of complex expressions in the sort list.

The BNF for a sorted item is:

sortExpression sortDirection? nullsPrecedence?sortExpression    : identifier | INTEGER_LITERAL | expressionsortDirection    : "ASC" | "DESC"nullsPrecedence    : "NULLS" ("FIRST" | "LAST")

Each sorted item listed in theorder by clause may explicitly specify a direction, either:

  • asc for ascending order, or

  • desc for descending order.

If no direction is explicitly specified, the results are returned in ascending order.

Of course, there’s an ambiguity with respect to null values.Therefore, the sorting of null values may be explicitly specified:

PrecedenceInterpretation

nulls first

Puts null values at the beginning of the result set

nulls last

Puts them at the end

selecttitle,publisher.namefromBookorderbytitle,publisher.namenullslast
selectbook.isbn,year(order.dateTime)asyear,sum(quantity)asyearlyTotalSold,sum(quantity*book.price)asyearlyTotalBilledfromItemwherebook.isbn=:isbngroupbyyear(order.dateTime)havingyear(order.dateTime)>2000andsum(quantity)>0orderbyyearlyTotalSolddesc,yeardesc

Queries with an ordered result list may have limits or pagination.

4.5.1. Limits and offsets

It’s often useful to place a hard upper limit on the number of results that may be returned by a query.Thelimit andoffset clauses are an alternative to the use ofsetMaxResults() andsetFirstResult() respectively,and may similarly be used for pagination.

If thelimit oroffset is parameterized, it’s much easier to usesetMaxResults() orsetFirstResult().

The SQLfetch syntax is supported as an alternative:

Short formVerbose formPurpose

limit 10

fetch first 10 rows only

Limit result set

limit 10 offset 20

offset 20 rows fetch next 10 rows only

Paginate result set

The BNF gets a bit complicated:

limitClause    : "LIMIT" parameterOrIntegerLiteraloffsetClause    : "OFFSET" parameterOrIntegerLiteral ("ROW" | "ROWS")?fetchClause    : "FETCH" ("FIRST" | "NEXT")      (parameterOrIntegerLiteral | parameterOrNumberLiteral "%")      ("ROW" | "ROWS")      ("ONLY" | "WITH" "TIES")

These two queries are identical:

selecttitlefromBookorderbytitle,publisheddesclimit50
selecttitlefromBookorderbytitle,publisheddescfetchfirst50rowsonly

These are well-defined limits: the number of results returned by the database will be limited to 50, as promised.But not every query is quite so well-behaved.

Limiting certainlyisn’t a well-defined relational operation, and must be used with care.

In particular, limits don’t play well withfetch joins.

This next query is accepted by HQL, and no more than 50 results are returned bygetResultList(), just as expected:

selecttitlefromBookjoinfetchauthorsorderbytitle,publisheddesclimit50

However, if you log the SQL executed by Hibernate, you’ll notice something wrong:

selectb1_0.isbn,a1_0.books_isbn,a1_0.authors_ORDER,a1_1.id,a1_1.bio,a1_1.name,a1_1.person_id,b1_0.price,b1_0.published,b1_0.publisher_id,b1_0.titlefromBookb1_0join(Book_Authora1_0joinAuthora1_1ona1_1.id=a1_0.authors_id)onb1_0.isbn=a1_0.books_isbnorderbyb1_0.title,b1_0.publisheddesc

What happened to thelimit clause?

When limits or pagination are combined with a fetch join, Hibernate must retrieve all matching results from the database andapply the limit in memory!

Thisalmost certainly isn’t the behavior you were hoping for, and in general will exhibitterrible performance characteristics.

4.6. Common table expressions

Acommon table expression or CTE may be thought of as a sort of named subquery.Any query with an uncorrelated subquery can in principle be rewritten so that the subquery occurs in thewith clause.

But CTEs have capabilities that subqueries don’t have.Thewith clause lets us:

  • specify materialization hints, and

  • write recursive queries.

On databases which don’t support CTEs natively, Hibernate attempts to rewrite any HQL query with CTEs as a SQL query with subqueries.This is impossible for recursive queries, unfortunately.

Let’s take a quick look at the BNF:

withClause: "WITH" cte ("," cte)*cte: identifier AS ("NOT"? "MATERIALIZED")? "(" queryExpression ")"      searchClause? cycleClause?

Thewith clause comes right at the start of a query.It may declare multiple CTEs with different names.

withpaidas(selectord.idasoid,sum(payment.amount)asamountPaidfromOrderasordleftjoinord.paymentsaspaymentgroupbyordhavinglocaldatetime-ord.dateTime<365day),owedas(selectord.idasoid,sum(item.quantity*item.book.price)asamountOwedfromOrderasordleftjoinord.itemsasitemgroupbyordhavinglocaldatetime-ord.dateTime<365day)selectid,paid.amountPaid,owed.amountOwedfromOrderwherepaid.amountPaid<owed.amountOwedandpaid.oid=idandowed.oid=id

Notice that if we rewrote this query using subqueries, it would look quite a lot clumsier.

4.6.1. Materialization hints

Thematerialized keyword is a hint to the database that the subquery should be separately executed and its results stored in a temporary table.

On the other hand, its nemesis,not materialized, is a hint that the subquery should be inlined at each use site, with each usage optimized independently.

The precise impact of materialization hints is quite platform-dependant.

Our example query from above hardly changes.We just addmaterialized to the CTE declarations.

withpaidasmaterialized(selectord.idasoid,sum(payment.amount)asamountPaidfromOrderasordleftjoinord.paymentsaspaymentgroupbyordhavinglocaldatetime-ord.dateTime<365day),owedasmaterialized(selectord.idasoid,sum(item.quantity*item.book.price)asamountOwedfromOrderasordleftjoinord.itemsasitemgroupbyordhavinglocaldatetime-ord.dateTime<365day)selectid,paid.amountPaid,owed.amountOwedfromOrderwherepaid.amountPaid<owed.amountOwedandpaid.oid=idandowed.oid=id

4.6.2. Recursive queries

Arecursive query is one where the CTE is defined self-referentially.Recursive queries follow a very particular pattern.The CTE is defined as a union of:

  • a base subquery returning an initial set of rows where the recursion begins,

  • a recursively-executed subquery which returns additional rows by joining against the CTE itself.

Let’s demonstrate this with an example.

First we’ll need some sort of tree-like entity:

@EntityclassNode{@IdLongid;Stringtext;@ManyToOneNodeparent;}

We may obtain a tree ofNodes with the following recursive query:

withTreeas(/* base query */selectroot.idasid,root.textastext,0aslevelfromNoderootwhereroot.parentisnullunionall/* recursion */selectchild.idasid,child.textastext,level+1aslevelfromTreeparentjoinNodechildonchild.parent.id=parent.id)selecttext,levelfromTree

When querying a tree-like of data structure, the base subquery usually returns the root node or nodes.The recursively-executed subquery returns the children of the current set of nodes.It’s executed repeatedly with the results of the previous execution.Recursion terminates when the recursively-executed subquery returns no new nodes.

Hibernate cannot emulate recursive queries on databases which don’t support them natively.

Now, if a graph contains cycles, that is, if it isn’t a tree, the recursion might never terminate.

4.6.3. Cycle detection

Thecycle clause enables cycle detection, and aborts the recursion if a node is encountered twice.

withTreeas(/* base query */selectroot.idasid,root.textastext,0aslevelfromNoderootwhereroot.parentisnullunionall/* recursion */selectchild.idasid,child.textastext,level+1aslevelfromTreeparentjoinNodechildonchild.parent.id=parent.id)cycleidsetabortto'aborted!'default''/* cycle detection */selecttext,level,abortfromTreeorderbylevel

Here:

  • theid column is used to detect cycles, and

  • theabort column is set to the string value'aborted!' if a cycle is detected.

Hibernate emulates thecycle clause on databases which don’t support it natively.

The BNF forcycle is:

cycleClause    : "CYCLE" identifier ("," identifier)*      "SET" identifier ("TO" literal "DEFAULT" literal)?      ("USING" identifier)?

The column optionally specified byusing holds the path to the current row.

4.6.4. Ordering depth-first or breadth-first

Thesearch clause allows us to control whether we would like the results of our query returned in an order that emulates a depth-first recursive search, or a breadth-first recursive search.

In our query above, we explicitly coded alevel column that holds the recursion depth, and ordered our result set according to this depth.With thesearch clause, that bookkeeping is already taken care of for us.

For depth-first search, we have:

withTreeas(/* base query */selectroot.idasid,root.textastextfromNoderootwhereroot.parentisnullunionall/* recursion */selectchild.idasid,child.textastextfromTreeparentjoinNodechildonchild.parent.id=parent.id)searchdepthfirstbyidsetlevel/* depth-first search */fromTreeselecttextorderbylevel

And for breadth-first search, we only need to change a single keyword:

withTreeas(/* base query */selectroot.idasid,root.textastextfromNoderootwhereroot.parentisnullunionall/* recursion */selectchild.idasid,child.textastextfromTreeparentjoinNodechildonchild.parent.id=parent.id)searchbreadthfirstbyidsetlevel/* breadth-first search */fromTreeselecttextorderbyleveldesc

Hibernate emulates thesearch clause on databases which don’t support it natively.

The BNF forsearch is:

searchClause    : "SEARCH" ("BREADTH"|"DEPTH") "FIRST"      "BY" searchSpecifications      "SET" identifiersearchSpecifications    : searchSpecification ("," searchSpecification)*searchSpecification    : identifier sortDirection? nullsPrecedence?

5. Credits

The full list of contributors to Hibernate ORM can be found on theGitHub repository.

The following contributors were involved in this documentation:

  • Gavin King

Version 6.6.37.Final
Last updated 2025-11-24 14:19:08 UTC

[8]ページ先頭

©2009-2025 Movatter.jp