HHH-11034 - Query.stream() Javadoc should specify whether the resulting Stream contains resources
This commit is contained in:
parent
52c79e1d3c
commit
531ff6e944
|
@ -107,7 +107,9 @@ include::{sourcedir}/BatchTest.java[tags=batch-session-scroll-example]
|
|||
|
||||
[IMPORTANT]
|
||||
====
|
||||
You should always close the `ScrollableResults`, to deallocate the associated `PreparedStatement` and avoid resource leaking.
|
||||
If left unclosed by the application, Hibernate will automatically close the underlying resources (e.g. `ResultSet` and `PreparedStatement`) used internally by the `ScrollableResults` when the current transaction is ended (either commit or rollback).
|
||||
|
||||
However, it is good practice to close the `ScrollableResults` explicitly.
|
||||
====
|
||||
|
||||
==== StatelessSession
|
||||
|
|
|
@ -295,12 +295,81 @@ include::{sourcedir}/HQLTest.java[tags=hql-api-list-example]
|
|||
----
|
||||
====
|
||||
|
||||
It is also possible to extract a single result from a `Query`.
|
||||
|
||||
[[hql-api-unique-result-example]]
|
||||
.Hibernate `uniqueResult()`
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/HQLTest.java[tags=hql-api-unique-result-example]
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If the unique result is used often and the attributes upon which it is based are unique, you may want to consider mapping a natural-id and using the natural-id loading API.
|
||||
See the <<chapters/domain/natural_id.adoc#naturalid,Natural Ids>> for more information on this topic.
|
||||
====
|
||||
|
||||
[[hql-api-scroll]]
|
||||
==== Query scrolling
|
||||
|
||||
Hibernate offers additional, specialized methods for scrolling the query and handling results using a server-side cursor.
|
||||
|
||||
`Query#scroll` works in tandem with the JDBC notion of a scrollable `ResultSet`.
|
||||
|
||||
The `Query#scroll` method is overloaded:
|
||||
|
||||
* Then main form accepts a single argument of type `org.hibernate.ScrollMode` which indicates the type of scrolling to be used.
|
||||
See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/ScrollMode.html[Javadocs] for the details on each.
|
||||
* The second form takes no argument and will use the `ScrollMode` indicated by `Dialect#defaultScrollMode`.
|
||||
|
||||
`Query#scroll` returns a `org.hibernate.ScrollableResults` which wraps the underlying JDBC (scrollable) `ResultSet` and provides access to the results.
|
||||
Unlike a typical forward-only `ResultSet`, the `ScrollableResults` allows you to navigate the `ResultSet` in any direction.
|
||||
|
||||
[[hql-api-scroll-example]]
|
||||
.Scrolling through a `ResultSet` containing entities
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/HQLTest.java[tags=hql-api-scroll-example]
|
||||
----
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
Since this form holds the JDBC `ResultSet` open, the application should indicate when it is done with the `ScrollableResults` by calling its `close()` method (as inherited from `java.io.Closeable`
|
||||
so that `ScrollableResults` will work with https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html[try-with-resources] blocks).
|
||||
|
||||
If left unclosed by the application, Hibernate will automatically close the underlying resources (e.g. `ResultSet` and `PreparedStatement`) used internally by the `ScrollableResults` when the current transaction is ended (either commit or rollback).
|
||||
|
||||
However, it is good practice to close the `ScrollableResults` explicitly.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If you plan to use `Query#scroll` with collection fetches it is important that your query explicitly order the results so that the JDBC results contain the related rows sequentially.
|
||||
====
|
||||
|
||||
Hibernate also supports `Query#iterate`, which is intended for loading entities when it is known that the loaded entries are already stored in the second-level cache.
|
||||
The idea behind iterate is that just the matching identifiers will be obtained in the SQL query.
|
||||
From these the identifiers are resolved by second-level cache lookup.
|
||||
If these second-level cache lookups fail, additional queries will need to be issued against the database.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This operation can perform significantly better for loading large numbers of entities that for certain already exist in the second-level cache.
|
||||
In cases where many of the entities do not exist in the second-level cache, this operation will almost definitely perform worse.
|
||||
====
|
||||
|
||||
The `Iterator` returned from `Query#iterate` is actually a specially typed Iterator: `org.hibernate.engine.HibernateIterator`.
|
||||
It is specialized to expose a `close()` method (again, inherited from `java.io.Closeable`).
|
||||
When you are done with this `Iterator` you should close it, either by casting to `HibernateIterator` or `Closeable`, or by calling https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Hibernate.html#close-java.util.Iterator-[`Hibernate#close(java.util.Iterator)`].
|
||||
|
||||
Since 5.2, Hibernate offers support for returning a `Stream` which can be later used to transform the underlying `ResultSet`.
|
||||
|
||||
Internally, the `stream()` behaves like a `Query#scroll` and the underlying result is backed by a `ScrollableResults`.
|
||||
====
|
||||
|
||||
Fetching a projection using the `Query#stream` method can be done as follows:
|
||||
|
||||
|
@ -324,54 +393,11 @@ include::{sourcedir}/HQLTest.java[tags=hql-api-stream-example]
|
|||
----
|
||||
====
|
||||
|
||||
It is also possible to extract a single result from a `Query`.
|
||||
|
||||
[[hql-api-unique-result-example]]
|
||||
.Hibernate `uniqueResult()`
|
||||
[IMPORTANT]
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/HQLTest.java[tags=hql-api-unique-result-example]
|
||||
----
|
||||
Just like with `ScrollableResults`, you should always close a Hibernate `Stream` either explicitly or using a https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html[try-with-resources] block.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If the unique result is used often and the attributes upon which it is based are unique, you may want to consider mapping a natural-id and using the natural-id loading API.
|
||||
See the <<chapters/domain/natural_id.adoc#naturalid,Natural Ids>> for more information on this topic.
|
||||
====
|
||||
|
||||
Hibernate offers 2 additional, specialized methods for performing the query and handling results.
|
||||
`Query#scroll` works in tandem with the JDBC notion of a scrollable `ResultSet`.
|
||||
The `scroll` method is overloaded.
|
||||
|
||||
* Then main form accepts a single argument of type `org.hibernate.ScrollMode` which indicates the type of scrolling to be used.
|
||||
See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/ScrollMode.html[Javadocs] for the details on each.
|
||||
* The second form takes no argument and will use the `ScrollMode` indicated by `Dialect#defaultScrollMode`.
|
||||
`Query#scroll` returns a `org.hibernate.ScrollableResults` which wraps the underlying JDBC (scrollable) `ResultSet` and provides access to the results.
|
||||
Since this form holds the JDBC `ResultSet` open, the application should indicate when it is done with the `ScrollableResults` by calling its `close()` method (as inherited from `java.io.Closeable` so that `ScrollableResults` will work with try-with-resources blocks!).
|
||||
If left unclosed by the application, Hibernate will automatically close the `ScrollableResults` when the current transaction completes.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If you plan to use `Query#scroll` with collection fetches it is important that your query explicitly order the results so that the JDBC results contain the related rows sequentially.
|
||||
====
|
||||
|
||||
The last is `Query#iterate`, which is intended for loading entities which the application feels certain will be in the second-level cache.
|
||||
The idea behind iterate is that just the matching identifiers will be obtained in the SQL query.
|
||||
From these the identifiers are resolved by second-level cache lookup.
|
||||
If these second-level cache lookups fail, additional queries will need to be issued against the database.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This operation can perform significantly better for loading large numbers of entities that for certain already exist in the second-level cache.
|
||||
In cases where many of the entities do not exist in the second-level cache, this operation will almost definitely perform worse.
|
||||
====
|
||||
|
||||
The `Iterator` returned from `Query#iterate` is actually a specially typed Iterator: `org.hibernate.engine.HibernateIterator`.
|
||||
It is specialized to expose a `close()` method (again, inherited from `java.io.Closeable`).
|
||||
When you are done with this `Iterator` you should close it, either by casting to `HibernateIterator` or `Closeable`, or by calling https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Hibernate.html#close-java.util.Iterator-[`Hibernate#close(java.util.Iterator)`].
|
||||
|
||||
[[hql-case-sensitivity]]
|
||||
=== Case Sensitivity
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.math.BigDecimal;
|
|||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -22,12 +23,12 @@ import javax.persistence.TemporalType;
|
|||
import javax.persistence.TypedQuery;
|
||||
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.ScrollableResults;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.MySQL5Dialect;
|
||||
import org.hibernate.dialect.Oracle8iDialect;
|
||||
import org.hibernate.dialect.PostgreSQL81Dialect;
|
||||
import org.hibernate.jpa.QueryHints;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.type.StringType;
|
||||
import org.hibernate.userguide.model.AddressType;
|
||||
|
@ -48,6 +49,7 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
|
@ -790,44 +792,95 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Session session = entityManager.unwrap( Session.class );
|
||||
//tag::hql-api-stream-example[]
|
||||
Stream<Person> persons = session.createQuery(
|
||||
try( Stream<Person> persons = session.createQuery(
|
||||
"select p " +
|
||||
"from Person p " +
|
||||
"where p.name like :name" )
|
||||
.setParameter( "name", "J%" )
|
||||
.stream();
|
||||
.stream() ) {
|
||||
|
||||
Map<Phone, List<Call>> callRegistry = persons
|
||||
.flatMap( person -> person.getPhones().stream() )
|
||||
.flatMap( phone -> phone.getCalls().stream() )
|
||||
.collect(Collectors.groupingBy(Call::getPhone));
|
||||
Map<Phone, List<Call>> callRegistry = persons
|
||||
.flatMap( person -> person.getPhones().stream() )
|
||||
.flatMap( phone -> phone.getCalls().stream() )
|
||||
.collect( Collectors.groupingBy( Call::getPhone ) );
|
||||
|
||||
process(callRegistry);
|
||||
}
|
||||
//end::hql-api-stream-example[]
|
||||
|
||||
assertEquals( 1, callRegistry.size() );
|
||||
});
|
||||
}
|
||||
|
||||
private void process(Map<Phone, List<Call>> callRegistry) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_hql_api_stream_projection_example() {
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Session session = entityManager.unwrap( Session.class );
|
||||
//tag::hql-api-stream-projection-example[]
|
||||
Stream<Object[]> persons = session.createQuery(
|
||||
try ( Stream<Object[]> persons = session.createQuery(
|
||||
"select p.name, p.nickName " +
|
||||
"from Person p " +
|
||||
"where p.name like :name" )
|
||||
.setParameter( "name", "J%" )
|
||||
.stream();
|
||||
.stream() ) {
|
||||
|
||||
List<PersonNames> personNames = persons
|
||||
.map( row -> new PersonNames( (String) row[0], (String)row[1] ) )
|
||||
.collect(Collectors.toList());
|
||||
persons
|
||||
.map( row -> new PersonNames(
|
||||
(String) row[0],
|
||||
(String) row[1] ) )
|
||||
.forEach( this::process );
|
||||
}
|
||||
//end::hql-api-stream-projection-example[]
|
||||
|
||||
assertEquals( 1, personNames.size() );
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_hql_api_scroll_projection_example() {
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Session session = entityManager.unwrap( Session.class );
|
||||
//tag::hql-api-scroll-example[]
|
||||
try ( ScrollableResults scrollableResults = session.createQuery(
|
||||
"select p " +
|
||||
"from Person p " +
|
||||
"where p.name like :name" )
|
||||
.setParameter( "name", "J%" )
|
||||
.scroll()
|
||||
) {
|
||||
while(scrollableResults.next()) {
|
||||
Person person = (Person) scrollableResults.get()[0];
|
||||
process(person);
|
||||
}
|
||||
}
|
||||
//end::hql-api-scroll-example[]
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_hql_api_scroll_open_example() {
|
||||
ScrollableResults scrollableResults = doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Session session = entityManager.unwrap( Session.class );
|
||||
return session.createQuery(
|
||||
"select p " +
|
||||
"from Person p " +
|
||||
"where p.name like :name" )
|
||||
.setParameter( "name", "J%" )
|
||||
.scroll();
|
||||
});
|
||||
try {
|
||||
scrollableResults.next();
|
||||
fail("Should throw exception because the ResultSet must be closed by now!");
|
||||
}
|
||||
catch ( Exception expected ) {
|
||||
}
|
||||
}
|
||||
|
||||
private void process(Person person) {
|
||||
}
|
||||
|
||||
private void process(PersonNames personName) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_hql_api_unique_result_example() {
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
|
|
|
@ -382,7 +382,12 @@ public interface Query<R> extends TypedQuery<R>, CommonQueryContract {
|
|||
/**
|
||||
* Return the query results as <tt>ScrollableResults</tt>. The
|
||||
* scrollability of the returned results depends upon JDBC driver
|
||||
* support for scrollable <tt>ResultSet</tt>s.<br>
|
||||
* support for scrollable <tt>ResultSet</tt>s.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* You should call {@link ScrollableResults#close()} after processing the <tt>ScrollableResults</tt>
|
||||
* so that the underlying resources are deallocated right away.
|
||||
*
|
||||
* @see ScrollableResults
|
||||
*
|
||||
|
@ -394,6 +399,11 @@ public interface Query<R> extends TypedQuery<R>, CommonQueryContract {
|
|||
* Return the query results as ScrollableResults. The scrollability of the returned results
|
||||
* depends upon JDBC driver support for scrollable ResultSets.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* You should call {@link ScrollableResults#close()} after processing the <tt>ScrollableResults</tt>
|
||||
* so that the underlying resources are deallocated right away.
|
||||
*
|
||||
* @param scrollMode The scroll mode
|
||||
*
|
||||
* @return the result iterator
|
||||
|
|
|
@ -91,6 +91,11 @@ public interface Query<R> extends TypedQuery<R>, org.hibernate.Query<R>, CommonQ
|
|||
* In the initial implementation (5.2) this returns a simple sequential Stream. The plan
|
||||
* is to return a a smarter stream in 6.0 leveraging the SQM model.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* You should call {@link java.util.stream.Stream#close()} after processing the stream
|
||||
* so that the underlying resources are deallocated right away.
|
||||
*
|
||||
* @return The results Stream
|
||||
*
|
||||
* @since 5.2
|
||||
|
|
Loading…
Reference in New Issue