HHH-11034 - Query.stream() Javadoc should specify whether the resulting Stream contains resources

This commit is contained in:
Vlad Mihalcea 2016-11-02 12:25:30 +02:00
parent 52c79e1d3c
commit 531ff6e944
5 changed files with 160 additions and 64 deletions

View File

@ -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

View File

@ -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

View File

@ -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 -> {

View File

@ -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

View File

@ -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