diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index 33f439b93..95ad6eff5 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Query for performing native SQL queries that return DTO Bean's. @@ -45,6 +46,26 @@ public interface DtoQuery { @Nonnull List findList(); + /** + * Execute the query iterating a row at a time. + *

+ * Note that the QueryIterator holds resources related to the underlying + * resultSet and potentially connection and MUST be closed. We should use + * QueryIterator in a try with resource block. + */ + @Nonnull + QueryIterator findIterate(); + + /** + * Execute the query returning a Stream. + *

+ * Note that the Stream holds resources related to the underlying + * resultSet and potentially connection and MUST be closed. We should use + * the Stream in a try with resource block. + */ + @Nonnull + Stream findStream(); + /** * Execute the query iterating a row at a time. *

diff --git a/ebean-api/src/main/java/io/ebean/QueryIterator.java b/ebean-api/src/main/java/io/ebean/QueryIterator.java index c158e62c6..2ac789073 100644 --- a/ebean-api/src/main/java/io/ebean/QueryIterator.java +++ b/ebean-api/src/main/java/io/ebean/QueryIterator.java @@ -56,7 +56,7 @@ import java.util.Iterator; * * @param the type of entity bean in the iteration */ -public interface QueryIterator extends Iterator, java.io.Closeable { +public interface QueryIterator extends Iterator, AutoCloseable { /** * Returns true if the iteration has more elements. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index a2e572904..daf6418d0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -1,14 +1,6 @@ package io.ebeaninternal.api; -import io.ebean.DtoQuery; -import io.ebean.EbeanServer; -import io.ebean.ExtendedServer; -import io.ebean.PersistenceContextScope; -import io.ebean.Query; -import io.ebean.RowConsumer; -import io.ebean.RowMapper; -import io.ebean.Transaction; -import io.ebean.TxScope; +import io.ebean.*; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.CallOrigin; import io.ebean.config.DatabaseConfig; @@ -25,6 +17,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Service Provider extension to EbeanServer. @@ -254,6 +247,16 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect */ void findEachRow(SpiSqlQuery query, RowConsumer consumer); + /** + * DTO findIterate query. + */ + QueryIterator findDtoIterate(SpiDtoQuery query); + + /** + * DTO findStream query. + */ + Stream findDtoStream(SpiDtoQuery query); + /** * DTO findList query. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index c39addd6a..231f0b718 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1433,14 +1433,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Nonnull @Override public Stream findStream(Query query, Transaction transaction) { - SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction); - try { - request.initTransIfRequired(); - return toStream(request.findIterate()); - } catch (RuntimeException ex) { - request.endTransIfRequired(); - throw ex; - } + return toStream(findIterate(query, transaction)); } private Stream toStream(QueryIterator queryIterator) { @@ -1640,6 +1633,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } + @Override + public QueryIterator findDtoIterate(SpiDtoQuery query) { + DtoQueryRequest request = new DtoQueryRequest<>(this, dtoQueryEngine, query); + try { + request.initTransIfRequired(); + return request.findIterate(); + } catch (RuntimeException ex) { + request.endTransIfRequired(); + throw ex; + } + } + + @Override + public Stream findDtoStream(SpiDtoQuery query) { + return toStream(findDtoIterate(query)); + } + @Override public List findDtoList(SpiDtoQuery query) { DtoQueryRequest request = new DtoQueryRequest<>(this, dtoQueryEngine, query); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java index b988c83b5..f4cdbc236 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.core; +import io.ebean.QueryIterator; import io.ebean.core.type.DataReader; import io.ebeaninternal.api.SpiDtoQuery; import io.ebeaninternal.api.SpiEbeanServer; @@ -90,6 +91,11 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest { } } + public QueryIterator findIterate() { + flushJdbcBatchOnQuery(); + return queryEngine.findIterate(this); + } + public void findEach(Consumer consumer) { flushJdbcBatchOnQuery(); queryEngine.findEach(this, consumer); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java index bbd111f1c..46de28b8b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryEngine.java @@ -1,10 +1,12 @@ package io.ebeaninternal.server.query; +import io.ebean.QueryIterator; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.DtoQueryRequest; import io.ebeaninternal.server.persist.Binder; import javax.persistence.PersistenceException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -34,6 +36,15 @@ public class DtoQueryEngine { } } + public QueryIterator findIterate(DtoQueryRequest request) { + try { + request.executeSql(binder, SpiQuery.Type.ITERATE); + return new DtoQueryIterator<>(request); + } catch (SQLException e) { + throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e); + } + } + public void findEach(DtoQueryRequest request, Consumer consumer) { try { request.executeSql(binder, SpiQuery.Type.ITERATE); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java new file mode 100644 index 000000000..cb22fdd3a --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DtoQueryIterator.java @@ -0,0 +1,55 @@ +package io.ebeaninternal.server.query; + +import io.ebean.QueryIterator; +import io.ebeaninternal.server.core.DtoQueryRequest; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; + +class DtoQueryIterator implements QueryIterator { + + private final DtoQueryRequest request; + private boolean closed; + + DtoQueryIterator(DtoQueryRequest request) { + this.request = request; + } + + @Override + public boolean hasNext() { + boolean result = false; + try { + result = request.next(); + return result; + } catch (SQLException e) { + throw new PersistenceException(e); + } finally { + if (!result) { + close(); + } + } + } + + @Override + public T next() { + try { + return request.readNextBean(); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public void close() { + if (!closed) { + closed = true; + request.close(); + request.endTransIfRequired(); + } + } + + @Override + public void remove() { + throw new IllegalStateException("remove() not allowed"); + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java index d5877572a..9afcb961f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.DtoQuery; import io.ebean.ProfileLocation; +import io.ebean.QueryIterator; import io.ebean.Transaction; import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.SpiDtoQuery; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Default implementation of DtoQuery. @@ -112,6 +114,16 @@ public class DefaultDtoQuery implements SpiDtoQuery { server.findDtoEachWhile(this, consumer); } + @Override + public QueryIterator findIterate() { + return server.findDtoIterate(this); + } + + @Override + public Stream findStream() { + return server.findDtoStream(this); + } + @Override public List findList() { return server.findDtoList(this); diff --git a/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java b/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java index 679fddd63..ba30e9a5c 100644 --- a/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java +++ b/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java @@ -6,10 +6,13 @@ import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -30,6 +33,73 @@ public class DtoQuery2Test extends BaseTestCase { assertThat(list).isNotEmpty(); } + @Test + public void dto_findIterator_closeWithResources() { + ResetBasicData.reset(); + + int counter = 0; + try (QueryIterator iterator = server() + .findDto(DCust.class, "select id, name from o_customer where id > ?") + .setParameter(0) + .findIterate()) { + + if (iterator.hasNext()) { + counter++; + } + } + + assertThat(counter).isEqualTo(1); + } + + @Test + public void dto_findIterator() { + ResetBasicData.reset(); + final int expectedCount = server().find(Customer.class).findCount(); + + LoggedSqlCollector.start(); + int counter = 0; + try (final QueryIterator iterator = server().findDto(DCust.class, "select id, name from o_customer where id > :id") + .setParameter("id", 0) + .findIterate()) { + + while (iterator.hasNext()) { + final DCust cust = iterator.next(); + counter++; + assertThat(cust).isNotNull(); + assertThat(cust.getName()).isNotNull(); + } + } + + assertThat(counter).isEqualTo(expectedCount); + + List sql = LoggedSqlCollector.stop(); + assertSql(sql.get(0)).contains("select id, name from o_customer where id > ?"); + } + + @Test + public void dto_findStream() { + ResetBasicData.reset(); + final int expectedCount = server().find(Customer.class).findCount(); + + LoggedSqlCollector.start(); + + try (final Stream stream = + server() + .findDto(DCust.class, "select id, name from o_customer where id > ?") + .setParameter(0) + .findStream()) { + + final List names = stream + .map(DCust::getName) + .collect(Collectors.toList()); + + assertThat(names.size()).isEqualTo(expectedCount); + } + + List sql = LoggedSqlCollector.stop(); + assertSql(sql.get(0)).contains("select id, name from o_customer where id > ?"); + } + @Test public void dto_findEach_constructorMatch() { diff --git a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 71e73b59f..f0f0c0779 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -453,6 +453,16 @@ public class TDSpiEbeanServer implements SpiEbeanServer { public void findDtoEachWhile(SpiDtoQuery query, Predicate consumer) { } + @Override + public QueryIterator findDtoIterate(SpiDtoQuery query) { + return null; + } + + @Override + public Stream findDtoStream(SpiDtoQuery query) { + return null; + } + @Override public List findDtoList(SpiDtoQuery query) { return null;