split up chapters of HQL

This commit is contained in:
Gavin 2023-05-27 21:01:52 +02:00 committed by Gavin King
parent 9360af5d6b
commit f3fddf02da
8 changed files with 3362 additions and 3338 deletions

View File

@ -194,6 +194,8 @@ tasks.register('renderIntroduction', AsciidoctorTask) {task->
}
}
// HQL Guide ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tasks.register('renderQLPdf', AsciidoctorPdfTask) {task->
group = "Documentation"
description = 'Renders the Query Language document in PDF format using Asciidoctor.'

View File

@ -0,0 +1,488 @@
[[basic-concepts]]
== Basic concepts
This document describes Hibernate Query Language (HQL), which is, in some sense, a dialect of the Java (now Jakarta) Persistence Query Language (JPQL).
Or is it the other way around?
[NOTE]
====
JPQL was inspired by early versions of HQL, and is a 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 setting `hibernate.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.
[[and-sqk]]
=== 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:
[source,sql]
----
select book.title, pub.name
from Book as book
join Publisher as pub
on book.publisherId = pub.id
where book.title like 'Hibernate%'
order by book.title
----
Then we bet you can already make sense of this HQL:
[source,sql]
----
select book.title, pub.name
from Book as book
join book.publisher as pub
where book.title like 'Hibernate%'
order by book.title
----
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.
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`, and `delete`.
[WARNING]
====
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.
[[case-sensitivity]]
=== Identifiers and case sensitivity
An identifier is a name used to refer to an entity, an attribute of a Java class, an <<identification-variables,identification variable>>, or a function.
For example, `Book`, `title`, `author`, and `upper` 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.
[%unbreakable]
[NOTE]
====
Incidentally, it's standard practice to use lowercase keywords in HQL and JPQL.
The use of uppercase keywords indicates an endearing but unhealthy attachment to the culture of the 1970's.
====
Just to reiterate these rules:
[cols="45,~"]
|===
| `select`, `SeLeCT`, `sELEct`, and `SELECT` | All the same, `select` is a keyword
| `upper(name)` and `UPPER(name)` | Same, `upper` is a function name
| `from BackPack` and `from Backpack` | Different, refer to different Java classes
| `person.nickName` and `person.nickname` | Different, since the path expression element `nickName` refers to an attribute of an entity defined in Java
| `person.nickName`, `Person.nickName`, and `PERSON.nickName` | All different, since the first element of a path expression is an <<identification-variables,identification variable>>
|===
[CAUTION]
====
The JPQL specification defines identification variables as case-_insensitive_.
And so in strict JPA-compliant mode, Hibernate treats `person.nickName`, `Person.nickName`, and `PERSON.nickName` as the _same_.
====
A _quoted identifier_ is written in backticks. Quoting lets you use a keyword as an identifier, for example `` thing.\`select` ``.
[[type-system]]
=== 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 like `Long`, `Float`, or `BigInteger`, date/time expressions have types like `LocalDate`, `LocalDateTime`, or `Instant`, and boolean expressions are always of type `Boolean`.
Going further, an expression like `local datetime - document.created` is assigned the Java type `java.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.
[[null-values-and-ternary-logic]]
==== Null values and ternary logic
The SQL `null` behaves quite differently to a null value in Java.
In Java, an expression like `number + 1` produces in an exception if `number` is null.
But in SQL, and therefore also in HQL and JPQL, such an expression evaluates to `null`.
[IMPORTANT]
====
It's almost always the case that an operation applied to a null value yields another null value.
This applies to function application, to operators like `*` and `||`, to comparison operators like `<` and `=`, and even to logical operations like `and` and `not`.
The exceptions to this rule are the functions `coalesce()` and `ifnull()` which are specifically designed for <<functions-null,dealing with null values>>.
====
This rule is the source of the famous (and controversial) _ternary logic_ of SQL.
A logical expression like `firstName='Gavin' and team='Hibernate'` isn't restricted to the values `true` and `false`.
It may also be `null`.
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 once never run into a case where this caused us problems.
As you probably know, when a logical predicate occurs as a <<where-clause,restriction>>, rows for which the predicate evaluates to `null` are _excluded_ from the result set.
That is, in this context at least, a logical null is interpreted as "effectively false".
[[statement-types]]
=== Statement types
HQL features four different kinds of statement:
- `select` queries,
- `update` statements,
- `delete` statements, and
- `insert ... values` and `insert ... select` statements.
Collectively, `insert`, `update`, and `delete` statements are sometimes called _mutation queries_.
We need to be a little bit careful when executing mutation queries via a stateful session.
[IMPORTANT]
====
The effect of an `update` or `delete` 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 an `update` or `delete` statement.
====
Let's consider each type of mutation query in turn, beginning with the most useful type.
[[update]]
==== Update statements
The https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form[BNF] for an `update` statement is quite straightforward:
[[update-bnf-example]]
[source, antlrv4]
----
include::{extrasdir}/statement_update_bnf.txt[]
----
The `set` clause has a list of assignments to attributes of the given entity.
For example:
[[update-example]]
[source, hql]
----
update Person set nickName = 'Nacho' where name = 'Ignacio'
----
Update statements are polymorphic, and affect mapped subclasses of the given entity class.
Therefore, a single HQL `update` statement might result in multiple SQL update statements executed against the database.
An `update` statement must be executed using `Query.executeUpdate()`.
[[update-examples]]
[source, java]
----
// JPA API
int updatedEntities = entityManager.createQuery(
"update Person p set p.name = :newName where p.name = :oldName")
.setParameter("oldName", oldName)
.setParameter("newName", newName)
.executeUpdate();
----
[source, java]
----
// Hibernate native API
int updatedEntities = session.createMutationQuery(
"update Person set name = :newName where name = :oldName")
.setParameter("oldName", oldName)
.setParameter("newName", newName)
.executeUpdate();
----
The integer value returned by `executeUpdate()` indicates the number of entity instances affected by the operation.
[NOTE]
====
In a `JOINED` 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.
====
An `update` statement, by default, does not affect the column mapped by the `@Version` attribute of the affected entities.
Adding the keyword `versioned`—writing `update versioned`—specifies that Hibernate should increment the version number or update the last modification timestamp.
// [NOTE]
// ====
// `update versioned` does not work with custom version types defined by implementing `UserVersionType`, and is not available in JPQL.
// ====
[[update-versioned-example]]
[source, hql]
----
update versioned Book set title = :newTitle where ssn = :ssn
----
Unfortunately, an `update` statement may not directly join other entities, not even using an <<implicit-join,implicit join>>, but it may have subqueries in its `set` clause, or in the `where` clause, which may contain joins.
[[delete]]
==== Delete statements
The BNF for a `delete` statement is even simpler:
[[delete-bnf-example]]
[source, antlrv4]
----
include::{extrasdir}/statement_delete_bnf.txt[]
----
For example:
[source,hql]
----
delete Author author where is empty author.books
----
As in SQL, the presence or absence of the `from` keyword has absolutely no effect on the semantics of the `update` statement.
Just like update statements, delete statements are polymorphic, and affect mapped subclasses of the given entity class.
Therefore, a single HQL `delete` statement might result in multiple SQL delete statements executed against the database.
A `delete` statement is executed by calling `Query.executeUpdate()`.
The integer value returned by `executeUpdate()` indicates the number of entity instances affected by the operation.
A `delete` statement may not directly join other entities, but it may have subqueries in the `where` clause, which may contain joins.
[[insert]]
==== Insert statements
There are two kinds of `insert` 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 the `values` clause.
The second form may insert many new rows, or none at all.
[%unbreakable]
[TIP]
====
The first sort of `insert` statement is not as useful.
It's usually better to just use `persist()`.
On the other hand, you might consider using it to set up test data.
====
[NOTE]
====
`insert` statements are not part of JPQL.
====
The BNF for an `insert` statement is:
[[insert-bnf-example]]
[source, antlrv4]
----
include::{extrasdir}/statement_insert_bnf.txt[]
----
For example:
[[insert-example]]
[source, hql]
----
insert Person (id, name)
values (100L, 'Jane Doe'), (200L, 'John Roe')
----
[source, hql]
----
insert into Author (id, name, bio)
select id, name, name || ' is a newcomer for ' || str(year(local date))
from Person
where id = :pid
----
As in SQL, the presence or absence of the `into` keyword has no effect on the semantics of the `insert` statement.
From these examples we might notice that `insert` statements are in one respect a bit different to `update` and `delete` statements.
[IMPORTANT]
====
An `insert` statement is inherently _not_ 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.
====
The `queryExpression` in an `insert ... select` statement may be any valid `select` query, with the caveat that the types of the values in the `select` list must match the types of the target fields.
[NOTE]
====
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 type `LocalDateTime` and an attribute or type `Timestamp` both map to the SQL type `timestamp`, 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.
Like `update` and `delete` statements, an `insert` statement must be executed by calling `Query.executeUpdate()`.
Now it's time to look at something _much_ more complicated.
[[select]]
==== Select statements
Select statements retrieve and analyse data.
This is what we're really here for.
The full BNF for a `select` query is quite complicated, but there's no need to understand it now.
We're displaying it here for future reference.
[[select-bnf-example]]
[source, antlrv4]
----
include::{extrasdir}/statement_select_bnf.txt[]
----
Most of the complexity here arises from the interplay of set operators (`union`, `intersect`, and `except`) with sorting.
We'll describe the various clauses of a query later, in <<root-entities-and-joins>> and in <<selection-projection-aggregation>>, but for now, to summarize, a query might have these bits:
[cols="22,22,~"]
|===
| Clause | Jargon | Purpose
| `with` | Common table expressions | Declares <<with-cte,named subqueries>> to be used in the following query
| `from` and `join` | Roots and joins | <<from-clause,Specifies>> the entities involved in the query, and how they're <<join,related>> to each other
| `where` | Selection/restriction | Specifies a <<where-clause,restriction>> on the data returned by the query
| `group by`| Aggregation/grouping | Controls <<group-by,aggregation>>
| `having` | Selection/restriction | Specifies a <<having,restriction>> to apply _after_ aggregation
| `select` | Projection | Specifies a <<select-clause,projection>> (the things to return from the query)
| `union`, `intersect`, `except` | Set algebra | These are <<set-operators,set operators>> applied to the results of multiple subqueries
| `order by` | Ordering | Specifies how the results should be <<order-by,sorted>>
| `limit`, `offset`, `fetch` | Limits | Allows for <<limit-offset,limiting or paginating>> the results
|===
Every one of these clauses is optional!
For example, the simplest query in HQL has no `select` clause at all:
[[select-simplest-example]]
[source, hql]
----
from Book
----
But we don't necessarily _recommend_ leaving off the `select` list.
[NOTE]
====
HQL doesn't require a `select` clause, but JPQL _does_.
====
Naturally, the previous query may be written with a `select` clause:
[source, hql]
----
select book from Book book
----
But when there's no explicit `select` clause, the select list is implied by the result type of the query:
[source, java]
[%unbreakable]
----
// result type Book, only the Book selected
List<Book> books =
session.createQuery("from Book join authors", Book.class)
.getResultList();
for (Person person: persons) {
...
}
----
[source, java]
[%unbreakable]
----
// result type Object[], both Book and Author selected
List<Object[]> booksWithAuthors =
session.createQuery("from Book join authors", Book.class, Object[].class)
.getResultList();
for (var bookWithAuthor: booksWithAuthors) {
Book book = (Book) bookWithAuthor[0];
Author author = (Author) bookWithAuthor[1];
...
}
----
For complicated queries, it's probably best to explicitly specify a `select` list.
An alternative "simplest" query has _only_ a `select` list:
[[select-simplest-example-alt]]
[source, hql]
----
select local datetime
----
This results in a SQL `from dual` query (or equivalent).
[TIP]
====
Looking carefully at the BNF given above, you might notice that the `select` list may occur either at the beginning of a query, or near the end, right before `order by`.
Of course, standard SQL, and JPQL, require that the `select` list comes at the beginning.
But it's more natural to put it last:
[source, hql]
----
from Book book select book.title, book.isbn
----
This form of the query is more readable, because the alias is declared _before_ it's used, just as God and nature intended.
====
Of course, queries are always polymorphic.
Indeed, a fairly innocent-looking HQL query can easily translate to a SQL statement with many joins and unions.
[TIP]
====
We need to be a _bit_ 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.
====

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,572 @@
[[root-entities-and-joins]]
== Root entities and joins
The `from` clause, and its subordinate `join` clauses sit right at the heart of most queries.
[[from-clause]]
=== Declaring root entities
The `from` 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_.
[[identification-variables]]
==== 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.
[TIP]
====
The identification variable is actually optional, but for queries involving more than one entity it's almost always a good idea to declare one.
This _works_, but it isn't particularly good form:
[source,hql]
----
from Publisher join books join authors join person where ssn = :ssn
----
====
Identification variables may be declared with the `as` keyword, but this is optional.
[[root-reference]]
==== Root entity references
A root entity reference, or what the JPQL specification calls a _range variable declaration_, is a direct reference to a mapped `@Entity` type by its entity name.
[TIP]
====
Remember, the _entity name_ is the value of the `name` member of the `@Entity` annotation, or the unqualified Java class name by default.
====
[[root-reference-jpql-example]]
[source, hql]
----
select book from Book as book
----
In this example, `Book` is the entity name, and `book` is the identification variable.
The `as` keyword is optional.
Alternatively, a fully-qualified Java class name may be specified.
Then Hibernate will query every entity which inherits the named type.
[[root-reference-jpql-fqn-example]]
[source, hql]
----
select doc from org.hibernate.example.AbstractDocument where text like :pattern
----
Of course, there may be multiple root entities.
[[multiple-root-reference-jpql-example]]
[source, hql]
----
select a, b
from Author a, Author b, Book book
where a in elements(book.authors)
and b in elements(book.authors)
----
This query may even be written using the syntax `cross join` in place of the commas:
[[cross-join-jpql-example]]
[source, hql]
----
select a, b
from Book book
cross join Author a
cross join Author b
where a in elements(book.authors)
and b in elements(book.authors)
----
Of course, it's possible to write old-fashioned pre-ANSI-era joins:
[source, hql]
----
select book.title, publisher.name
from Book book, Publisher publisher
where book.publisher = publisher
and book.title like :titlePattern
----
But we never write HQL this way.
[[polymorphism]]
==== Polymorphism
HQL and JPQL queries are inherently polymorphic.
Consider:
[[polymorphism-example]]
[source, hql]
----
select payment from Payment as payment
----
This query names the `Payment` entity explicitly.
But the `CreditCardPayment` and `WireTransferPayment` entities inherit `Payment`, and so `payment` ranges over all three types.
Instances of all these entities are returned by the query.
[NOTE]
====
The query `from java.lang.Object` is completely legal. (But not very useful!)
It returns every object of every mapped entity type.
====
// This behavior may be slightly adjusted using the `@Polymorphism` annotation.
//
// See <<chapters/domain/inheritance.adoc#entity-inheritance-polymorphism>> for more.
[[derived-root]]
==== Derived roots
A _derived root_ is an uncorrelated subquery which occurs in the `from` clause.
[[derived-root-example]]
[source, hql]
----
select id, total
from (
select ord.id as id, sum(item.book.price * item.quantity) as total
from Order as ord
join Item as item
group by ord
)
where total > 100.0
----
The derived root may declare an identification variable.
[source, hql]
----
select stuff.id, stuff.total
from (
select ord.id as id, sum(item.book.price * item.quantity) as total
from Order as ord
join Item as item
group by ord
) as stuff
where total > 100.0
----
This feature can be used to break a more complicated query into smaller pieces.
[IMPORTANT]
====
We emphasize that a derived root must be an _uncorrelated_ subquery.
It may not refer to other roots declared in the same `from` clause.
====
A subquery may also occur in a <<join-derived, join>>, in which case it may be a correlated subquery.
[[from-cte]]
==== Common table expressions in `from` clause
A _common table expression (CTE)_ is like a derived root with a name.
We'll discuss CTEs <<with-cte,later>>.
[[join]]
=== 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 the `from` clause using the keyword ``join``, and
- _implicit joins_, which don't need to be declared in the `from` clause.
An explicit join may be either:
* an _inner join_, written as `join` or `inner join`,
* a _left outer join_, written as `left join` or `left outer join`,
* a _right outer join_, written as `right join` or `right outer join`, or
* a _full outer join_, written as `full join` or `full outer join`.
[[root-join]]
==== Explicit root joins
An explicit root join works just like an ANSI-style join in SQL.
[[explicit-root-join-example]]
[source, hql]
----
select book.title, publisher.name
from Book book
join Publisher publisher
on book.publisher = publisher
where book.title like :titlePattern
----
The join condition is written out explicitly in the `on` clause.
[NOTE]
====
This looks nice and familiar, but it's _not_ the most common sort of join in HQL or JPQL.
====
[[explicit-join]]
==== 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 have _two_ 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.
[[explicit-inner-join-example]]
[source, hql]
----
from Book as book
join book.publisher as publisher
join book.authors as author
where book.title like :titlePattern
select book.title, author.name, publisher.name
----
For an outer join, we must write our query to accommodate the possibility that the joined association is missing.
[[explicit-outer-join-example]]
[source, hql]
----
from Book as book
left join book.publisher as publisher
join book.authors as author
where book.title like :titlePattern
select book.title, author.name, ifnull(publisher.name, '-')
----
For further information about collection-valued association references, see <<collection-valued-associations>>.
[[explicit-join-conditions]]
==== Explicit association joins with join conditions
The `with` or `on` clause allows explicit qualification of the join conditions.
[NOTE]
====
The specified join conditions are _added_ to the join conditions specified by the foreign key association.
That's why, historically, HQL uses the keword `with` here:
"with" emphasizes that the new condition doesn't _replace_ the original join conditions.
The `with` keyword is specific to Hibernate. JPQL uses `on`.
====
Join conditions occurring in the `with` or `on` clause are added to the `on` clause in the generated SQL.
[[explicit-join-with-example]]
[source, hql]
----
from Book as book
left join book.publisher as publisher
with publisher.closureDate is not null
left join book.authors as author
with author.type <> COLLABORATION
where book.title like :titlePattern
select book.title, author.name, publisher.name
----
// The following query is arguably less clear, but it's semantically identical:
//
// [[explicit-join-jpql-on-example]]
// [source, hql]
// ----
// from Book as book
// left join book.publisher as publisher
// on publisher.closureDate is not null
// left join book.authors as author
// on author.type <> COLLABORATION
// where book.title like :titlePattern
// select book.title, author.name, publisher.name
// ----
[[explicit-fetch-join]]
==== Association fetching
A _fetch 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.
* A `join fetch`, or, more explicitly, `inner join fetch`, only returns base entities with an associated entity.
* A `left join fetch`, or—for lovers of verbosity—``left outer join fetch``, returns all the base entities, including those which have no associated joined entity.
[IMPORTANT]
====
This is one of the most important features of Hibernate.
To achieve acceptable performance with HQL, you'll need to use `join fetch` quite often.
Without it, you'll quickly run into the dreaded "n+1 selects" problem.
====
For example, if `Person` has a one-to-many association named `phones`, the use of `join fetch` in the following query specifies that the collection elements should be fetched in the same SQL query:
[[explicit-fetch-join-example]]
[source, hql]
----
select book
from Book as book
left join fetch book.publisher
join fetch book.authors
----
In this example, we used a left outer join for `book.publisher` because we also wanted to obtain books with no publisher, but a regular inner join for `book.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 of _nested_ fetch joins is also fine, but
* fetching multiple collections or to-many associations in _parallel_ 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 a ``join fetch``ed 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.
[IMPORTANT]
====
Fetch joins should usually be avoided in limited or paged queries.
This includes:
- queries executed with limits specified via the `setFirstResult()` and `setMaxResults()` methods of `Query`, or
- queries with a limit or offset declared in HQL, described below in <<limit-offset>>.
Nor should they be used with the `scroll()` and `stream()` methods of the `Query` interface.
====
Fetch joins are disallowed in subqueries, where they would make no sense.
[[join-treat]]
==== Joins with typecasts
An explicit join may narrow the type of the joined entity using `treat()`.
[[join-treat-example]]
[source, hql]
----
from Order as ord
join treat(ord.payments as CreditCardPayment) as creditCardPayment
where length(creditCardPayment.cardNumber) between 16 and 20
----
Here, the identification variable `ccp` declared to the right of `treat()` has the narrowed type `CreditCardPayment`, instead of the declared type `Payment`.
This allows the attribute `cardNumber` declared by the subtype `CreditCardPayment` to be referenced in the rest of the query.
See <<functions-typecasts>> for more information about `treat()`.
[[join-derived]]
==== Subqueries in joins
A `join` clause may contain a subquery, either:
- an uncorrelated subquery, which is almost the same as a <<derived-root,derived root>>, except that it may have an `on` restriction, or
- a _lateral join_, which is a correlated subquery, and may refer to other roots declared earlier in the same `from` clause.
The `lateral` keyword just distinguishes the two cases.
[[derived-join-example]]
[source, hql]
----
from Phone as phone
left join (
select call.duration as duration, call.phone.id as cid
from Call as call
order by call.duration desc
limit 1
) as longest on cid = phone.id
where phone.number = :phoneNumber
select longest.duration
----
This query may also be expressed using a `lateral` join:
[source, hql]
----
from Phone as phone
left join lateral (
select call.duration as duration
from phone.calls as call
order by call.duration desc
limit 1
) as longest
where phone.number = :phoneNumber
select longest.duration
----
A lateral join may be an inner or left outer join, but not a right join, nor a full join.
[TIP]
====
Traditional SQL doesn't allow correlated subqueries in the `from` clause.
A lateral join is essentially just that, but with a different syntax to what you might expect.
On some databases, `join lateral` is written `cross apply`.
And on Postgres it's plain `lateral`, without `join`.
It's almost as if they're _deliberately trying_ to confuse us.
====
Lateral joins are particularly useful for computing top-N elements of multiple groups.
[IMPORTANT]
====
Most databases support some flavor of `join 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.
====
[[implicit-join]]
==== Implicit association joins (path expressions)
It's not necessary to explicitly `join` every entity that occurs in a query.
Instead, entity associations may be _navigated_, 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 of `value()`, `element()`, or `key()`.
It's clear that:
* A path expression like `author.name` with only two elements just refers to state held directly by an entity with an alias `author` defined in `from` or `join`.
* 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.
[[implicit-join-example]]
[source, hql]
----
from Book as book
where book.publisher.name like :pubName
----
As in this example, implicit joins usually appear outside the `from` clause of the HQL query.
However, they always affect the `from` clause of the SQL query.
The example above is equivalent to:
[[implicit-join-alt]]
[source, hql]
[%unbreakable]
----
select book
from Book as book
join book.publisher pub
where pub.name like :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:
[[implicit-join-alias-example]]
[source, hql]
----
select book
from Book as book
where book.publisher.name like :pubName
and book.publisher.closureDate is null
----
results in just one SQL join, and is just a different way to write:
[[implicit-join-alias-alt]]
[source, hql]
----
select book
from Book as book
join book.publisher as pub
where pub.name like :pubName
and pub.closureDate is null
----
[[collection-valued-associations]]
==== Joining collections and many-valued associations
When a join involves a collection or many-valued association, the declared identification variable refers to the _elements_ of the collection, that is:
- to the elements of a `Set`,
- to the elements of a `List`, not to their indices in the list, or
- to the values of a `Map`, not to their keys.
[[collection-valued-associations-example]]
[source, hql]
----
select publisher.name, author.name
from Publisher as publisher
join publisher.books as book
join book.authors author
where author.name like :namePattern
----
In this example, the identification variable `author` is of type `Author`, the element type of the list `Book.authors`.
But if we need to refer to the index of an `Author` in the list, we need some extra syntax.
You might recall that we mentioned <<list-functions>> and <<map-functions>> a bit earlier.
These functions may be applied to the identification variable declared in a collection join or many-valued association join.
[cols="12,20,~,~"]
|===
| Function | Applies to | Interpretation | Notes
| `value()` or `element()` | Any collection | The collection element or map entry value
| Often optional.
| `index()` | Any `List` with an index column | The index of the element in the list
| For backward compatibility, it's also an alternative to ``key()``, when applied to a map.
| `key()` | Any `Map` | The key of the entry in the list | If the key is of entity type, it may be further navigated.
| `entry()` | Any `Map` | The map entry, that is, the `Map.Entry` of key and value.
| Only legal as a terminal path, and only allowed in the `select` clause.
|===
In particular, `index()` and `key()` obtain a reference to a list index or map key.
[[collection-qualification-example]]
[source, hql]
[%unbreakable]
----
select book.title, author.name, index(author)
from Book as book
join book.authors as author
----
[source, hql]
[%unbreakable]
----
select publisher.name, leadAuthor.name
from Publisher as publisher
join publisher.books as book
join book.authors leadAuthor
where leadAuthor.name like :namePattern
and index(leadAuthor) == 0
----
[[implicit-collection-join]]
==== Implicit joins involving collections
A path expression like `book.authors.name` is not considered legal.
We can't just navigate a many-valued association with this syntax.
Instead, the functions `element()`, `index()`, `key()`, and `value()` may be applied to a path expression to express an implicit join.
So we must write `element(book.authors).name`.
[[collection-implicit-join-example]]
[source, hql]
----
select book.title, element(book.authors).name, index(book.authors)
from Book book
----
An element of an indexed collection (an array, list, or map) may even be identified using the index operator:
[[collection-index-operator-example]]
[source, hql]
----
select publisher.name, book.authors[0].name
from Publisher as publisher
join publisher.books as book
where book.authors[0].name like :namePattern
----

View File

@ -0,0 +1,15 @@
[[preface]]
== 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 HQL 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.
But if you don't already know Hibernate, don't start here.
First, read _An introduction to Hibernate 6_, and then come back.

File diff suppressed because it is too large Load Diff

View File

@ -247,6 +247,14 @@ task stageIntroduction(type: Copy) {
into "${buildDir}/documentation/introduction"
}
task stageQL(type: Copy) {
group 'Release'
dependsOn ':documentation:renderQL'
from "${project( ':documentation' ).buildDir}/asciidoc/querylanguage"
into "${buildDir}/documentation/querylanguage"
}
task stageUserGuide(type: Copy) {
group 'Release'