minor improvements to new HQL guide

This commit is contained in:
Gavin 2023-05-28 00:59:18 +02:00
parent f3fddf02da
commit fab058a3a1
4 changed files with 85 additions and 48 deletions

View File

@ -957,7 +957,7 @@ where total > 100.0
----
[[between-predicate]]
==== `between` predicate
==== The `between` predicate
The ternary `between` operator, and its negation, `not between`, determine if a value falls within a range.
@ -988,6 +988,32 @@ The following operators make it easier to deal with null values.
from Author where nomDePlume is not null
----
[[collection-operators]]
==== Collection predicates
The following operators apply to collection-valued attributes and to-many associations.
[cols="15,15,20,~"]
|===
| Operator | Negation | Type | Semantics
| `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
|===
[[empty-collection-predicate-example]]
[source, hql]
----
from Author where books is empty
----
[[member-of-collection-predicate-example]]
[source, hql]
----
from Author as author, Book as book
where author member of book.authors
----
[[like-predicate]]
==== String pattern matching
@ -1019,7 +1045,7 @@ The optional `escape` character allows a pattern to include a literal `_` or `%`
As you can guess, `not like` and `not ilike` are the enemies of `like` and `ilike`, and evaluate to the exact opposite boolean values.
[[in-predicate]]
==== `in` predicate
==== The `in` predicate
The `in` predicates evaluates to true if the value to its left is in ... well, whatever it finds to its right.
@ -1063,6 +1089,11 @@ where type(payment) in (CreditCardPayment, WireTransferPayment)
from Author as author
where author.person.name in (select name from OldAuthorData)
----
[source, hql]
----
from Book as book
where :edition in elements(book.editions)
----
This example doesn't work on every database:
@ -1118,7 +1149,7 @@ from Publisher pub where :title = some(select title from pub.books)
----
[[exists-predicate]]
==== `exists` predicate
==== The `exists` predicate
The unary prefix `exists` operator evaluates to true if the thing to its right is nonempty.
@ -1143,34 +1174,6 @@ where exists (
)
----
[[collection-operators]]
==== Collection predicates
The following operators apply to collection-valued attributes and to-many associations.
[cols="15,15,20,~"]
|===
| Operator | Negation | Type | Semantics
| `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
|===
[[empty-collection-predicate-example]]
[source, hql]
----
from Author where books is empty
----
[[member-of-collection-predicate-example]]
[source, hql]
----
from Author as author, Book as book
where author member of book.authors
----
[[logical-operators]]
==== Logical operators

View File

@ -452,7 +452,7 @@ The example above is equivalent to:
----
select book
from Book as book
join book.publisher pub
join book.publisher as pub
where pub.name like :pubName
----

View File

@ -101,6 +101,7 @@ So let's restrict our result set to data from our own more civilized times:
[[group-by-having-example]]
[source, hql]
[%unbreakable]
----
select book.isbn,
year(order.dateTime) as year,
@ -120,13 +121,21 @@ The `having` restriction is applied after grouping and aggregation has already b
=== Projection
The `select` list identifies which objects and values to return as the query results.
This operation is called _projection_.
NOTE: This operation is called _projection_.
[source,antlrv4]
----
selectClause
: "SELECT" "DISTINCT"? selection (","" selection)*
----
Any of the expression types discussed in <<expressions>> may occur in the projection list, unless otherwise noted.
TIP: If a query has no explicit `select` list, the projection is inferred from the entities and joins occurring in the `from` clause, together with the result type specified by the call to `createQuery()`.
It's better to specify the projection explicitly, except in the simplest cases.
[TIP]
====
If a query has no explicit `select` list, then, as we saw <<select-simplest-example,much earlier>>, the projection is inferred from the entities and joins occurring in the `from` clause, together with the result type specified by the call to `createQuery()`.
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.
@ -134,13 +143,19 @@ Java doesn't have a good way to represent tuples.
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".
But if there are multiple expressions in the select list then:
[source, java]
[%unbreakable]
----
List<String> results =
entityManager.createQuery("select title from Book", String.class)
.getResultList();
----
- by default, each query result is packaged as an array of type `Object[]`, or
- if explicitly requested by passing the class `Tuple` to `createQuery()`, the query result is packaged as an instance of `javax.persistence.Tuple`.
But if there are multiple expressions in the select list then, by default, 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",
@ -151,7 +166,11 @@ for (var result : results) {
String preamble = (String) result[1];
}
----
Or, if explicitly requested by passing the class `Tuple` to `createQuery()`, the query result is packaged as an instance of `javax.persistence.Tuple`.
[source, java]
[%unbreakable]
----
List<Tuple> results =
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
@ -164,10 +183,9 @@ for (Tuple tuple : tuples) {
----
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 (positions are numbered from 0).
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.
Unfortunately, neither `Object[]` nor `Tuple` lets us access an individual item in a result tuple of an HQL query without explicitly specifying the type of the item.
(Using a typecast in the case of `Object[]`, or by passing the class object to `get()` in the case of `Tuple`.)
Unfortunately, neither `Object[]` nor `Tuple` lets us access an individual item in a result tuple of an HQL query without explicitly specifying the type of the item, either using a typecast in the case of `Object[]`, or by passing the class object to `get()` in the case of `Tuple`.
But there's another option, as we're about to see.
Simplifying slightly, the BNF for a projected item is:
@ -175,13 +193,20 @@ Simplifying slightly, the BNF for a projected item is:
[[select-item-bnf]]
[source, antlrv4]
----
include::{extrasdir}/select_item_bnf.txt[]
selection
: (expression | instantiation) alias?
instantiation
: "NEW" instantiationTarget "(" selection ("," selection)* ")"
alias
: "AS"? identifier
----
where `instantiatiationArgs` is essentially a nested projection list.
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.
// Let's see what it does.
[[select-new]]
==== Instantiation
@ -190,6 +215,7 @@ The `select new` construct packages the query results into a user-written Java c
[[select-clause-dynamic-instantiation-example]]
[source, java]
[%unbreakable]
----
record BookSummary(String title, String summary) {}
@ -216,6 +242,7 @@ Alternatively, using the syntax `select new map`, the query may specify that eac
[[select-clause-dynamic-map-instantiation-example]]
[source, java]
[%unbreakable]
----
List<Map> results =
entityManager.createQuery("select new map(title as title, left(book.text, 200) as summary) from Book",
@ -224,12 +251,13 @@ List<Map> results =
----
The keys of the map are determined by the aliases given to the projected items in the select list.
If no aliases are specified, the key of an item is its position in the list (positions are numbered from 0).
If no aliases are specified, the key of an item is its position in the list, where the first item is assigned the position zero.
Or, using the syntax `select new list`, the query may specify that each result should be packaged as a list:
[[select-clause-dynamic-list-instantiation-example]]
[source, java]
[%unbreakable]
----
List<List> results =
entityManager.createQuery("select new list(title as title, left(book.text, 200) as summary) from Book",
@ -242,7 +270,7 @@ List<List> results =
This is an older syntax, that predates JPQL.
In hindsight, it's hard to see what advantage `List<Object>` offers compared to `Object[]`.
We mention it here only for completeness.
On the other hand, `Map` is a perfectly fine alternative `Tuple`, but of course isn't portable to other implementations of JPA.
On the other hand, `Map` is a perfectly fine alternative `Tuple`, but of course it isn't portable to other implementations of JPA.
====
[[distinct]]
@ -295,18 +323,21 @@ The standard aggregate functions defined in both ANSI SQL and JPQL are these one
[[aggregate-functions-example]]
[source, hql]
[%unbreakable]
----
select count(distinct item.book)
from Item as item
where year(item.order.dateTime) = :year
----
[source, hql]
[%unbreakable]
----
select sum(item.quantity) as totalSales
from Item as item
where item.book.isbn = :isbn
----
[source, hql]
[%unbreakable]
----
select
year(item.order.dateTime) as year,
@ -316,6 +347,7 @@ where item.book.isbn = :isbn
group by year(item.order.dateTime)
----
[source, hql]
[%unbreakable]
----
select
month(item.order.dateTime) as month,
@ -385,6 +417,7 @@ All aggregate functions support the inclusion of a _filter clause_, a sort of mi
[[aggregate-functions-filter-example]]
[source, hql]
[%unbreakable]
----
select
year(item.order.dateTime) as year,

View File

@ -1,7 +1,8 @@
(expression | instantiation) alias?
selection
: (expression | instantiation) alias?
instantiation
: "NEW" instantiationTarget "(" instantiationArguments ")"
: "NEW" instantiationTarget "(" selection ("," selection)* ")"
alias
: "AS"? IDENTIFIER