update docs with implicit instantiation
This commit is contained in:
parent
72f03d9d0f
commit
cf74308318
|
@ -873,7 +873,7 @@ Java's `record` types now offer an interesting alternative:
|
|||
record IsbnTitle(String isbn, String title) {}
|
||||
|
||||
var results =
|
||||
session.createSelectionQuery("select new IsbnTitle(isbn, title) from Book", IsbnTitle.class)
|
||||
session.createSelectionQuery("select isbn, title from Book", IsbnTitle.class)
|
||||
.getResultList();
|
||||
|
||||
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.
|
||||
But perhaps you find it more aesthetically pleasing.
|
||||
|
||||
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.
|
||||
And if we're going to be passing query results around the system, the use of a `record` type is _much_ better.
|
||||
|
||||
Now, the criteria query API offers a much more satisfying solution to the problem.
|
||||
Consider the following code:
|
||||
|
|
|
@ -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:
|
||||
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".
|
||||
|
||||
|
@ -151,7 +154,7 @@ List<String> results =
|
|||
.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]]
|
||||
[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]
|
||||
[%unbreakable]
|
||||
----
|
||||
List<Tuple> results =
|
||||
List<Tuple> tuples =
|
||||
entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book",
|
||||
Tuple.class)
|
||||
.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.
|
||||
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.
|
||||
|
||||
[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]]
|
||||
|
@ -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.
|
||||
// Let's see what it does.
|
||||
|
||||
[[select-new]]
|
||||
==== Instantiation
|
||||
|
||||
The `select new` construct packages the query results into a user-written Java class instead of an array.
|
||||
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]
|
||||
|
@ -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.
|
||||
====
|
||||
|
||||
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]
|
||||
[%unbreakable]
|
||||
----
|
||||
List<Map> results =
|
||||
entityManager.createQuery("select new map(title as title, left(book.text, 200) as summary) from Book",
|
||||
Map.class)
|
||||
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();
|
||||
}
|
||||
----
|
||||
|
||||
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, where the first item is assigned the position zero.
|
||||
In the past, this functionality required more ceremony.
|
||||
|
||||
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]]
|
||||
[source, java]
|
||||
[%unbreakable]
|
||||
----
|
||||
List<List> results =
|
||||
entityManager.createQuery("select new list(title as title, left(book.text, 200) as summary) from Book",
|
||||
List.class)
|
||||
.getResultList();
|
||||
----
|
||||
| `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` | ✔/✖
|
||||
|===
|
||||
|
||||
[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]]
|
||||
==== Duplicate removal
|
||||
|
|
Loading…
Reference in New Issue