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) {}
|
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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue