move some material to 1st chapter of HQL guide

This commit is contained in:
Gavin 2023-05-29 00:57:52 +02:00 committed by Gavin King
parent cf74308318
commit ed75e24d94
2 changed files with 211 additions and 180 deletions

View File

@ -1,13 +1,13 @@
[[basic-concepts]]
== 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).
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?
[NOTE]
====
JPQL was inspired by early versions of HQL, and is a subset of modern HQL.
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 setting `hibernate.jpa.compliance.query=true`.
@ -117,7 +117,15 @@ 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` ``.
A _quoted identifier_ is written in backticks. Quoting lets you use a keyword as an identifier.
[source,hql]
----
select thing.interval.`from` from Thing thing
----
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.
[[comments]]
==== Comments
@ -183,13 +191,14 @@ Since the language must be executed on SQL databases, every type accommodates nu
==== 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`.
- 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`.
This rule 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 `is null` operator and the functions `coalesce()` and `ifnull()` which are specifically designed for <<functions-null,dealing with null values>>.
====
@ -344,7 +353,7 @@ The second form may insert many new rows, or none at all.
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.
But you might consider using it to set up test data.
====
[NOTE]
@ -527,7 +536,7 @@ 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.
Naturally, queries are always polymorphic.
Indeed, a fairly innocent-looking HQL query can easily translate to a SQL statement with many joins and unions.
[TIP]
@ -536,3 +545,196 @@ 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.
====
[[returning-to-java]]
=== 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.
[[query-result-types-single]]
==== Queries with a single projected item
If there's just one projected item in the `select` list, then, no sweat, that's the type of each query result.
[source, java]
[%unbreakable]
----
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 the `select` list of a query.
[[query-result-types-multiple]]
==== 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 type `Object[]`.
[[select-clause-projection-example]]
[source, java]
[%unbreakable]
----
List<Object[]> results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
Object[].class)
.getResultList();
for (var result : results) {
String title = (String) result[0];
String preamble = (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 of `javax.persistence.Tuple`.
All we have to do is pass the class `Tuple` to `createQuery()`.
[source, java]
[%unbreakable]
----
List<Tuple> tuples =
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
Tuple.class)
.getResultList();
for (Tuple tuple : tuples) {
String title = tuple.get("title", String.class);
String preamble = tuple.get("preamble", String.class);
}
----
The names of the `Tuple` 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 pass `Map` or `List`, and have each result packaged as a map or list:
[source, java]
[%unbreakable]
----
var results =
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
Map.class)
.getResultList();
for (var map : results) {
String title = (String) tuple.get("title");
String preamble = (String) tuple.get("preamble");
}
----
[source, java]
[%unbreakable]
----
var results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
List.class)
.getResultList();
for (var list : results) {
String title = (String) list.get(0);
String preamble = (String) tuple.get(1);
}
----
Unfortunately, not one of the types `Object[]`, `List`, `Map`, nor `Tuple` lets us access an individual item in a result tuple without a type cast.
Sure `Tuple` does the type cast for us when we pass a class object to `get()`, but it's logically identical.
Fortunately there's one more option, as we're about to see.
[NOTE]
====
Actually, `Tuple` really exists to service the criteria query API, and in that context it _does_ enable truly typesafe access to query results.
====
Hibernate 6 lets us pass an arbitrary class type with an appropriate constructor to `createQuery()` and will use it to package the query results.
This works extremely nicely with `record` types.
[[select-clause-implicit-instantiation-example]]
[source, java]
[%unbreakable]
----
record BookSummary(String title, String summary) {}
List<BookSummary> results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
BookSummary.class)
.getResultList();
for (var result : results) {
String title = result.title();
String preamble = result.summary();
}
----
It's important that the constructor of `BookSummary` has parameters which exactly match the items in the `select` list.
[NOTE]
====
This class does not need to be mapped or annotated in any way.
Even if the class _is_ an entity class, the resulting instances are _not_ managed entities and are _not_ 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 to `createQuery()`.
But at least we don't have to write them explicitly.
[[select-new]]
==== Instantiation
In JPA, and in older versions of Hibernate, this functionality required more ceremony.
[cols="25,~,~,^15"]
|===
| Result type | Legacy syntax | Streamlined syntax | JPA standard
| `Map` | `select new map(x, y)` | `select x, y` | ✖/✖
| `List` | `select new list(x, y)` | `select x, y` | ✖/✖
| Arbitrary class `Record` | `select new Record(x, y)` | `select x, y` | ✔/✖
|===
For example, the JPA-standard `select new` construct packages the query results into a user-written Java class instead of an array.
[[select-clause-dynamic-instantiation-example]]
[source, java]
[%unbreakable]
----
record BookSummary(String title, String summary) {}
List<BookSummary> results =
entityManager.createQuery("select new BookSummary(title, left(book.text, 200)) from Book",
BookSummary.class)
.getResultList();
for (var result : results) {
String title = result.title();
String preamble = result.summary();
}
----
Simplifying slightly, the BNF for a projected item is:
[[select-item-bnf]]
[source, antlrv4]
----
selection
: (expression | instantiation) alias?
instantiation
: "NEW" instantiationTarget "(" selection ("," selection)* ")"
alias
: "AS"? identifier
----
Where the list of ``selection``s in an `instantiation` is essentially a nested projection list.

View File

@ -137,177 +137,6 @@ If a query has no explicit `select` list, then, as we saw <<select-simplest-exam
But it's better to specify the projection explicitly, except in the simplest cases.
====
There might be multiple items in a projection list, in which case each query result is a tuple, and this poses a problem:
Java doesn't have a good way to represent tuples.
[[query-result-types]]
==== Default query result types
If there's just one projected item in the `select` list, then, no sweat, that's the type of each query result.
There's no need to bother with trying to represent a "tuple of length 1".
[source, java]
[%unbreakable]
----
List<String> results =
entityManager.createQuery("select title from Book", String.class)
.getResultList();
----
But if 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 type `Object[]`.
[[select-clause-projection-example]]
[source, java]
[%unbreakable]
----
List<Object[]> results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
Object[].class)
.getResultList();
for (var result : results) {
String title = (String) result[0];
String preamble = (String) result[1];
}
----
This is bearable, but let's explore some other options.
[[query-alt-result-types]]
==== Alternative generic result types
If explicitly requested by passing the class `Tuple` to `createQuery()`, the query result is packaged as an instance of `javax.persistence.Tuple`, again, as specified by JPA.
[source, java]
[%unbreakable]
----
List<Tuple> tuples =
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
Tuple.class)
.getResultList();
for (Tuple tuple : tuples) {
String title = tuple.get("title", String.class);
String preamble = tuple.get("preamble", String.class);
}
----
The names of the `Tuple` 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, Hibernate lets us pass `Map` or `List` here:
[source, java]
[%unbreakable]
----
var results =
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
Map.class)
.getResultList();
for (var map : results) {
String title = (String) tuple.get("title");
String preamble = (String) tuple.get("preamble");
}
----
[source, java]
[%unbreakable]
----
var results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
List.class)
.getResultList();
for (var list : results) {
String title = (String) list.get(0);
String preamble = (String) tuple.get(1);
}
----
Unfortunately, none of the types `Object[]`, `List`, `Map`, nor `Tuple` lets us access an individual item in a result tuple without explicitly specifying the type of the item, either using a typecast in the case of `Object[]`, `List`, or `Map`, or by passing the class object to `get()` in the case of `Tuple`.
But there's another option, as we're about to see.
[NOTE]
====
Actually, `Tuple` really exists to service the criteria query API, and in that context it _does_ enable truly typesafe access to query results.
====
[[select-new]]
==== Instantiation
Simplifying slightly, the BNF for a projected item is:
[[select-item-bnf]]
[source, antlrv4]
----
selection
: (expression | instantiation) alias?
instantiation
: "NEW" instantiationTarget "(" selection ("," selection)* ")"
alias
: "AS"? identifier
----
Where the list of ``selection``s in an `instantiation` is essentially a nested projection list.
So there's a special expression type that's only legal in the select clause: the `instantiation` rule in the BNF above.
// Let's see what it does.
This JPA-standard `select new` construct packages the query results into a user-written Java class instead of an array.
[[select-clause-dynamic-instantiation-example]]
[source, java]
[%unbreakable]
----
record BookSummary(String title, String summary) {}
List<BookSummary> results =
entityManager.createQuery("select new BookSummary(title, left(book.text, 200)) from Book",
BookSummary.class)
.getResultList();
for (var result : results) {
String title = result.title();
String preamble = result.summary();
}
----
The class must have a matching constructor.
[IMPORTANT]
====
This class does not need to be mapped or annotated in any way.
Even if the class _is_ an entity class, the resulting instances are _not_ managed entities and are _not_ associated with the session.
====
But Hibernate 6 goes one better and makes the `select new` syntax optional.
[[select-clause-implicit-instantiation-example]]
[source, java]
[%unbreakable]
----
record BookSummary(String title, String summary) {}
List<BookSummary> results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
BookSummary.class)
.getResultList();
for (var result : results) {
String title = result.title();
String preamble = result.summary();
}
----
In the past, this functionality required more ceremony.
[cols="25,~,~,^15"]
|===
| Result type | Legacy syntax | Streamlined syntax | JPA standard
| `Map` | `select new map(x, y)` | `select x, y` | ✖/✖
| `List` | `select new list(x, y)` | `select x, y` | ✖/✖
| Arbitrary class `Record` | `select new Record(x, y)` | `select x, y` | ✔/✖
|===
[[distinct]]
==== Duplicate removal