#811 - ENH: Restore findIterate() as alternative to findEach / findEachWhile

This commit is contained in:
Robin Bygrave
2016-08-05 13:01:42 +12:00
parent 6c182ecaa5
commit 43afaf1344
19 changed files with 211 additions and 70 deletions
@@ -761,6 +761,25 @@ public interface EbeanServer {
*/
<A> List<A> findIds(Query<?> query, Transaction transaction);
/**
* Return a QueryIterator for the query.
* <p>
* Generally using {@link #findEach(Query, QueryEachConsumer, Transaction)} or
* {@link #findEachWhile(Query, QueryEachWhileConsumer, Transaction)} is preferred
* to findIterate(). The reason is that those methods automatically take care of
* closing the queryIterator (and the underlying jdbc statement and resultSet).
* </p>
* <p>
* This is similar to findEach in that not all the result beans need to be held
* in memory at the same time and as such is good for processing large queries.
* </p>
*
* @see Query#findIterate()
* @see Query#findEach(QueryEachConsumer)
* @see Query#findEachWhile(QueryEachWhileConsumer)
*/
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
@@ -147,6 +147,13 @@ public interface ExpressionList<T> {
*/
int update();
/**
* Execute the query iterating over the results.
*
* @see Query#findIterate()
*/
QueryIterator<T> findIterate();
/**
* Execute the query process the beans one at a time.
*
+50
View File
@@ -544,6 +544,46 @@ public interface Query<T> {
*/
<A> List<A> findIds();
/**
* Execute the query iterating over the results.
* <p>
* Note that findIterate (and findEach and findEachWhile) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
* when you have finished iterating the results (typically in a finally block).
* </p>
* <p>
* findEach() and findEachWhile() are preferred to findIterate() as they ensure
* the jdbc statement and resultSet are closed at the end of the iteration.
* </p>
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
* <pre>{@code
*
* Query<Customer> query =
* ebeanServer.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id");
*
* QueryIterator<Customer> it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* } finally {
* // close the underlying resources
* it.close();
* }
*
* }</pre>
*/
QueryIterator<T> findIterate();
/**
* Execute the query processing the beans one at a time.
* <p>
@@ -552,6 +592,11 @@ public interface Query<T> {
* (unlike #findList #findSet etc)
* </p>
* <p>
* Note that findEach (and findEachWhile and findIterate) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
@@ -588,6 +633,11 @@ public interface Query<T> {
* Execute the query using callbacks to a visitor to process the resulting
* beans one at a time.
* <p>
* Note that findEachWhile (and findEach and findIterate) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
* with Java8 closures.
@@ -0,0 +1,67 @@
package com.avaje.ebean;
import java.util.Iterator;
/**
* Used to provide iteration over query results.
* <p>
* This can be used when you want to process a very large number of results and
* means that you don't have to hold all the results in memory at once (unlike
* findList(), findSet() etc where all the beans are held in the List or Set
* etc).
* </p>
* <p>
* Note that findIterate (and findEach and findEachWhile) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
* when you have finished iterating the results (typically in a finally block).
* </p>
*
* <pre>{@code
*
* Query<Customer> query = server.find(Customer.class)
* .where().gt("id", 0)
* .orderBy("id")
* .setMaxRows(2);
*
* QueryIterator<Customer> it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* } finally {
* // close the underlying resources
* it.close();
* }
*
* }</pre>
*
* @param <T>
* the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*/
T next();
/**
* Remove is not allowed.
*/
void remove();
/**
* Close the underlying resources held by this iterator.
*/
void close();
}
@@ -71,10 +71,8 @@ import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -1304,6 +1302,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new LimitOffsetPagedList<T>(this, spiQuery);
}
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
try {
request.initTransIfRequired();
return request.findIterate();
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
}
public <T> void findEach(Query<T> query, QueryEachConsumer<T> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
@@ -1,8 +1,8 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebeaninternal.api.BeanIdList;
import java.util.List;
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
@@ -12,7 +13,6 @@ import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.text.json.JsonReadOptions;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.HashQuery;
import com.avaje.ebeaninternal.api.LoadContext;
@@ -1,60 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.util.Iterator;
/**
* Used to provide iteration over query results.
* <p>
* This can be used when you want to process a very large number of results and
* means that you don't have to hold all the results in memory at once (unlike
* findList(), findSet() etc where all the beans are held in the List or Set
* etc).
* </p>
*
* <pre class="code">
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .fetch(&quot;contacts&quot;, new FetchConfig().query(2))
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
*
* QueryIterator&lt;Customer&gt; it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer...
* }
* } finally {
* // close the associated resources
* it.close();
* }
* </pre>
*
* @author rbygrave
*
* @param <T>
* the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*/
T next();
/**
* Remove is not allowed.
*/
void remove();
/**
* Close the underlying resources held by this iterator.
*/
void close();
}
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -358,6 +358,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.findIds();
}
@Override
public QueryIterator<T> findIterate() {
return query.findIterate();
}
@Override
public void findEach(QueryEachConsumer<T> consumer) {
query.findEach(consumer);
@@ -12,6 +12,7 @@ import com.avaje.ebean.PagedList;
import com.avaje.ebean.Query;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.search.Match;
@@ -375,6 +376,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.findIds();
}
@Override
public QueryIterator<T> findIterate() {
return exprList.findIterate();
}
@Override
public void findEach(QueryEachConsumer<T> consumer) {
exprList.findEach(consumer);
@@ -1,6 +1,6 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
@@ -10,7 +10,7 @@ import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.core.DiffHelp;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.lib.util.Str;
import com.avaje.ebeaninternal.server.persist.Binder;
@@ -4,7 +4,7 @@ import java.sql.SQLException;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
@@ -5,7 +5,7 @@ import java.util.ArrayList;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
@@ -1,11 +1,10 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryEngine;
@@ -1114,6 +1114,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
server.findEach(this, consumer, null);
}
@Override
public QueryIterator<T> findIterate() {
return server.findIterate(this, null);
}
@Override
public List<Version<T>> findVersions() {
this.temporalMode = TemporalMode.VERSIONS;
@@ -470,6 +470,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction) {
return null;
}
@Override
public <T> void findEach(Query<T> query, QueryEachConsumer<T> consumer, Transaction transaction) {
@@ -27,6 +27,32 @@ public class TestQueryFindIterate extends BaseTestCase {
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
.setMaxRows(2);
final AtomicInteger count = new AtomicInteger();
QueryIterator<Customer> it = query.findIterate();
try {
while (it.hasNext()) {
Customer customer = it.next();
customer.getName();
count.incrementAndGet();
}
} finally {
it.close();
}
assertEquals(2, count.get());
}
@Test
public void findEach() {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
.setAutoTune(false)
//.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).orderBy("id")