update docs with implicit instantiation

This commit is contained in:
Gavin 2023-05-28 18:46:36 +02:00 committed by Gavin King
parent 72f03d9d0f
commit cf74308318
2 changed files with 69 additions and 35 deletions

View File

@ -873,7 +873,7 @@ Java's `record` types now offer an interesting alternative:
record IsbnTitle(String isbn, String title) {} record IsbnTitle(String isbn, String title) {}
var results = var results =
session.createSelectionQuery("select new IsbnTitle(isbn, title) from Book", IsbnTitle.class) session.createSelectionQuery("select isbn, title from Book", IsbnTitle.class)
.getResultList(); .getResultList();
for (var result : results) { for (var result : results) {
@ -886,8 +886,7 @@ Notice that we're able to declare the `record` right before the line which execu
Now, this is only _superficially_ more typesafe, since the query itself is not checked statically, and so we can't say it's objectively better. Now, this is only _superficially_ more typesafe, since the query itself is not checked statically, and so we can't say it's objectively better.
But perhaps you find it more aesthetically pleasing. But perhaps you find it more aesthetically pleasing.
And if we're going to be passing query results around the system, the use of a `record` type is _much_ better.
On the other hand, when we're passing query results around the system, the use of `select new` with a `record` type is much better than manually unpacking an `Object[]` array.
Now, the criteria query API offers a much more satisfying solution to the problem. Now, the criteria query API offers a much more satisfying solution to the problem.
Consider the following code: Consider the following code:

View File

@ -140,6 +140,9 @@ But it's better to specify the projection explicitly, except in the simplest cas
There might be multiple items in a projection list, in which case each query result is a tuple, and this poses a problem: 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. 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. 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". There's no need to bother with trying to represent a "tuple of length 1".
@ -151,7 +154,7 @@ List<String> results =
.getResultList(); .getResultList();
---- ----
But if there are multiple expressions in the select list then, by default, each query result is packaged as an array of type `Object[]`. 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]] [[select-clause-projection-example]]
[source, java] [source, java]
@ -167,12 +170,17 @@ for (var result : results) {
} }
---- ----
Or, if explicitly requested by passing the class `Tuple` to `createQuery()`, the query result is packaged as an instance of `javax.persistence.Tuple`. 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] [source, java]
[%unbreakable] [%unbreakable]
---- ----
List<Tuple> results = List<Tuple> tuples =
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book", entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
Tuple.class) Tuple.class)
.getResultList(); .getResultList();
@ -185,9 +193,44 @@ for (Tuple tuple : tuples) {
The names of the `Tuple` elements are determined by the aliases given to the projected items in the select list. 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. 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, either using a typecast in the case of `Object[]`, or by passing the class object to `get()` in the case of `Tuple`. 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. 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: Simplifying slightly, the BNF for a projected item is:
[[select-item-bnf]] [[select-item-bnf]]
@ -208,10 +251,7 @@ Where the list of ``selection``s in an `instantiation` is essentially a nested p
So there's a special expression type that's only legal in the select clause: the `instantiation` rule in the BNF above. 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]] This JPA-standard `select new` construct packages the query results into a user-written Java class instead of an array.
==== Instantiation
The `select new` construct packages the query results into a user-written Java class instead of an array.
[[select-clause-dynamic-instantiation-example]] [[select-clause-dynamic-instantiation-example]]
[source, java] [source, java]
@ -238,40 +278,35 @@ 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. Even if the class _is_ an entity class, the resulting instances are _not_ managed entities and are _not_ associated with the session.
==== ====
Alternatively, using the syntax `select new map`, the query may specify that each result should be packaged as a map: But Hibernate 6 goes one better and makes the `select new` syntax optional.
[[select-clause-dynamic-map-instantiation-example]] [[select-clause-implicit-instantiation-example]]
[source, java] [source, java]
[%unbreakable] [%unbreakable]
---- ----
List<Map> results = record BookSummary(String title, String summary) {}
entityManager.createQuery("select new map(title as title, left(book.text, 200) as summary) from Book",
Map.class) List<BookSummary> results =
entityManager.createQuery("select title, left(book.text, 200) from Book",
BookSummary.class)
.getResultList(); .getResultList();
for (var result : results) {
String title = result.title();
String preamble = result.summary();
}
---- ----
The keys of the map are determined by the aliases given to the projected items in the select list. In the past, this functionality required more ceremony.
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: [cols="25,~,~,^15"]
|===
| Result type | Legacy syntax | Streamlined syntax | JPA standard
[[select-clause-dynamic-list-instantiation-example]] | `Map` | `select new map(x, y)` | `select x, y` | ✖/✖
[source, java] | `List` | `select new list(x, y)` | `select x, y` | ✖/✖
[%unbreakable] | Arbitrary class `Record` | `select new Record(x, y)` | `select x, y` | ✔/✖
---- |===
List<List> results =
entityManager.createQuery("select new list(title as title, left(book.text, 200) as summary) from Book",
List.class)
.getResultList();
----
[NOTE]
====
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 it isn't portable to other implementations of JPA.
====
[[distinct]] [[distinct]]
==== Duplicate removal ==== Duplicate removal