diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index 0bf18b041..4f582c591 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -1020,7 +1020,7 @@ public interface EbeanServer { SqlFutureList findFutureList(SqlQuery query, Transaction transaction); /** - * Return a PagedList for this query. + * Return a PagedList for this query using pageIndex and pageSize. *

* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and * {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to @@ -1042,6 +1042,38 @@ public interface EbeanServer { */ PagedList findPagedList(Query query, Transaction transaction, int pageIndex, int pageSize); + /** + * Return a PagedList for this query using firstRow and maxRows. + *

+ * The benefit of using this over findList() is that it provides functionality to get the + * total row count etc. + *

+ *

+ * If maxRows is not set on the query prior to calling findPagedList() then a + * PersistenceException is thrown. + *

+ * + *
{@code
+   *
+   *  PagedList pagedList = Ebean.find(Order.class)
+   *       .setFirstRow(50)
+   *       .setMaxRows(20)
+   *       .findPagedList();
+   *
+   *       // fetch the total row count in the background
+   *       pagedList.loadRowCount();
+   *
+   *       List orders = pagedList.getList();
+   *       int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
+ * + * @return The PagedList + * + * @see Query#findPagedList() + */ + PagedList findPagedList(Query query, Transaction transaction); + /** * Execute the query returning a set of entity beans. *

diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index b91237c20..f5b6e002a 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -245,7 +245,7 @@ public interface ExpressionList extends Serializable { FutureList findFutureList(); /** - * Return a PagedList for this query. + * Return a PagedList for this query using pageIndex and pageSize. *

* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and * {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to @@ -265,6 +265,38 @@ public interface ExpressionList extends Serializable { */ PagedList findPagedList(int pageIndex, int pageSize); + /** + * Return a PagedList for this query using firstRow and maxRows. + *

+ * The benefit of using this over findList() is that it provides functionality to get the + * total row count etc. + *

+ *

+ * If maxRows is not set on the query prior to calling findPagedList() then a + * PersistenceException is thrown. + *

+ * + *
{@code
+   *
+   *  PagedList pagedList = Ebean.find(Order.class)
+   *       .setFirstRow(50)
+   *       .setMaxRows(20)
+   *       .findPagedList();
+   *
+   *       // fetch the total row count in the background
+   *       pagedList.loadRowCount();
+   *
+   *       List orders = pagedList.getList();
+   *       int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
+ * + * @return The PagedList + * + * @see Query#findPagedList() + */ + PagedList findPagedList(); + /** * Return versions of a @History entity bean. *

diff --git a/src/main/java/com/avaje/ebean/PagedList.java b/src/main/java/com/avaje/ebean/PagedList.java index 52749948e..97fd2c04a 100644 --- a/src/main/java/com/avaje/ebean/PagedList.java +++ b/src/main/java/com/avaje/ebean/PagedList.java @@ -17,6 +17,33 @@ import java.util.concurrent.Future; * limit the result set. *

* + * + *

Example: typical use including total row count

+ *
{@code
+ *
+ *     // We want to find the first 50 new orders
+ *     //  ... so we don't really need setFirstRow(0)
+ *
+ *     PagedList pagedList
+ *       = ebeanServer.find(Order.class)
+ *       .where().eq("status", Order.Status.NEW)
+ *       .order().asc("id")
+ *       .setFirstRow(0)
+ *       .setMaxRows(50)
+ *       .findPagedList();
+ *
+ *     // Optional: initiate the loading of the total
+ *     // row count in a background thread
+ *     pagedList.loadRowCount();
+ *
+ *     // fetch and return the list in the foreground thread
+ *     List orders = pagedList.getList();
+ *
+ *     // get the total row count (from the future)
+ *     int totalRowCount = pagedList.getTotalRowCount();
+ *
+ * }
+ * *

Example: typical use including total row count

*
{@code
  *
@@ -156,11 +183,15 @@ public interface PagedList {
 
   /**
    * Return the index position of this page. Zero based.
+   * 

+ * Note that if firstRows/maxRows is used rather than pageIndex/pageSize then + * this always returns 0. + *

*/ int getPageIndex(); /** - * Return the page size used for this query. + * Return the page size used for this query. This is the same value as maxRows used by the query. */ int getPageSize(); diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index d1978b07a..074ced3c9 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -822,7 +822,7 @@ public interface Query extends Serializable { FutureList findFutureList(); /** - * Return a PagedList for this query. + * Return a PagedList for this query using pageIndex and pageSize. *

* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and * {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to @@ -867,6 +867,36 @@ public interface Query extends Serializable { */ PagedList findPagedList(int pageIndex, int pageSize); + /** + * Return a PagedList for this query using firstRow and maxRows. + *

+ * The benefit of using this over findList() is that it provides functionality to get the + * total row count etc. + *

+ *

+ * If maxRows is not set on the query prior to calling findPagedList() then a + * PersistenceException is thrown. + *

+ * + *
{@code
+   *
+   *  PagedList pagedList = Ebean.find(Order.class)
+   *       .setFirstRow(50)
+   *       .setMaxRows(20)
+   *       .findPagedList();
+   *
+   *       // fetch the total row count in the background
+   *       pagedList.loadRowCount();
+   *
+   *       List orders = pagedList.getList();
+   *       int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
+ * + * @return The PagedList + */ + PagedList findPagedList(); + /** * Set a named bind parameter. Named parameters have a colon to prefix the name. * diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 429665cc2..da07b5bf9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1384,6 +1384,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return new LimitOffsetPagedList(this, (SpiQuery)query, pageIndex, pageSize); } + @Override + public PagedList findPagedList(Query query, Transaction transaction) { + + SpiQuery spiQuery = (SpiQuery)query; + int maxRows = spiQuery.getMaxRows(); + if (maxRows == 0) { + throw new PersistenceException("maxRows must be specified for findPagedList() query"); + } + + return new LimitOffsetPagedList(this, spiQuery); + } + public void findEach(Query query, QueryEachConsumer consumer, Transaction t) { SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index 9b4e3bba2..85168ca5a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -314,6 +314,11 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr return exprList.findPagedList(pageIndex, pageSize); } + @Override + public PagedList findPagedList() { + return exprList.findPagedList(); + } + @Override public int findRowCount() { return exprList.findRowCount(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java index 46b34be19..3a61112e6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java @@ -21,7 +21,9 @@ public class LimitOffsetPagedList implements PagedList { private final SpiQuery query; - private final int pageSize; + private final int firstRow; + + private final int maxRows; private final int pageIndex; @@ -33,11 +35,29 @@ public class LimitOffsetPagedList implements PagedList { private List list; + /** + * Construct with pageIndex/pageSize. + */ public LimitOffsetPagedList(EbeanServer server, SpiQuery query, int pageIndex, int pageSize) { this.server = server; this.query = query; - this.pageSize = pageSize; + this.maxRows = pageSize; + this.firstRow = pageIndex * pageSize; this.pageIndex = pageIndex; + + query.setFirstRow(firstRow); + query.setMaxRows(pageSize); + } + + /** + * Construct with firstRow/maxRows. + */ + public LimitOffsetPagedList(EbeanServer server, SpiQuery query) { + this.server = server; + this.query = query; + this.maxRows = query.getMaxRows(); + this.firstRow = query.getFirstRow(); + this.pageIndex = 0; } public void loadRowCount() { @@ -56,8 +76,6 @@ public class LimitOffsetPagedList implements PagedList { public List getList() { synchronized (monitor) { if (list == null) { - query.setFirstRow(pageIndex * pageSize); - query.setMaxRows(pageSize); list = server.findList(query, null); } return list; @@ -70,7 +88,7 @@ public class LimitOffsetPagedList implements PagedList { if (rowCount == 0) { return 0; } else { - return ((rowCount - 1) / pageSize) + 1; + return ((rowCount - 1) / maxRows) + 1; } } @@ -94,11 +112,11 @@ public class LimitOffsetPagedList implements PagedList { } public boolean hasNext() { - return pageIndex < (getTotalPageCount() - 1); + return (firstRow + maxRows) < getTotalRowCount(); } public boolean hasPrev() { - return pageIndex > 0; + return firstRow > 0; } public int getPageIndex() { @@ -106,13 +124,13 @@ public class LimitOffsetPagedList implements PagedList { } public int getPageSize() { - return pageSize; + return maxRows; } public String getDisplayXtoYofZ(String to, String of) { - int first = pageIndex * pageSize + 1; - int last = first + getList().size() - 1; + int first = firstRow + 1; + int last = firstRow + getList().size(); int total = getTotalRowCount(); return first + to + last + of + total; diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index b3f5fdda2..ca801d039 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1094,6 +1094,11 @@ public class DefaultOrmQuery implements SpiQuery { return server.findPagedList(this, null, pageIndex, pageSize); } + @Override + public PagedList findPagedList() { + return server.findPagedList(this, null); + } + /** * Set an ordered bind parameter according to its position. Note that the position starts at 1 to * be consistent with JDBC PreparedStatement. You need to set a parameter value for each ? you diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index b2d84946d..f50c146de 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -201,6 +201,11 @@ public class DefaultExpressionList implements SpiExpressionList { return query.findPagedList(pageIndex, pageSize); } + @Override + public PagedList findPagedList() { + return query.findPagedList(); + } + @Override public int findRowCount() { return query.findRowCount(); diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 172601c66..5ff481340 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -512,6 +512,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public PagedList findPagedList(Query query, Transaction transaction) { + return null; + } + @Override public Set findSet(Query query, Transaction transaction) { return null; diff --git a/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java b/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java index 7c972fc26..5c3269db0 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java +++ b/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java @@ -8,6 +8,7 @@ import com.avaje.tests.model.basic.ResetBasicData; import org.avaje.ebeantest.LoggedSqlCollector; import org.junit.Test; +import javax.persistence.PersistenceException; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -16,10 +17,126 @@ import java.util.concurrent.TimeoutException; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class TestQueryFindPagedList extends BaseTestCase { + + @Test(expected = PersistenceException.class) + public void test_noMaxRows() throws ExecutionException, InterruptedException { + + Ebean.find(Order.class).findPagedList(); + } + + @Test + public void test_maxRows_NoCount() throws ExecutionException, InterruptedException { + + ResetBasicData.reset(); + + PagedList pagedList = Ebean.find(Order.class) + .setMaxRows(4) + .findPagedList(); + + LoggedSqlCollector.start(); + + List orders = pagedList.getList(); + + assertTrue(!orders.isEmpty()); + List loggedSql = LoggedSqlCollector.stop(); + + assertEquals("Only 1 SQL statement, no count query",1, loggedSql.size()); + } + + + @Test + public void test_maxRows_countInBackground() throws ExecutionException, InterruptedException, TimeoutException { + + ResetBasicData.reset(); + + PagedList pagedList = Ebean.find(Order.class) + .setMaxRows(3) + .findPagedList(); + + Future rowCount = pagedList.getFutureRowCount(); + List orders = pagedList.getList(); + + // these are each getting the total row count + int totalRowCount = pagedList.getTotalRowCount(); + Integer totalRowCountWithTimeout = rowCount.get(30, TimeUnit.SECONDS); + Integer totalRowCountViaFuture = rowCount.get(); + + assertTrue(orders.size() < totalRowCount); + assertEquals(Integer.valueOf(totalRowCount), totalRowCountViaFuture); + assertEquals(Integer.valueOf(totalRowCount), totalRowCountWithTimeout); + } + + @Test + public void test_maxRows_countInBackground_withLoadRowCount() throws InterruptedException { + + ResetBasicData.reset(); + + // fetch less that total orders (page size 3) + PagedList pagedList = Ebean.find(Order.class) + .setMaxRows(3) + .findPagedList(); + + pagedList.loadRowCount(); + List orders = pagedList.getList(); + int totalRowCount = pagedList.getTotalRowCount(); + + assertThat(orders.size()).isLessThan(totalRowCount); + assertTrue(pagedList.hasNext()); + assertFalse(pagedList.hasPrev()); + + + // fetch less that total orders (page size 3) + PagedList pagedList2 = Ebean.find(Order.class) + .setFirstRow(1) + .setMaxRows(3) + .findPagedList(); + + pagedList2.loadRowCount(); + List orders2 = pagedList2.getList(); + int totalRowCount2 = pagedList2.getTotalRowCount(); + assertTrue(pagedList2.hasNext()); + assertTrue(pagedList2.hasPrev()); + + assertThat(totalRowCount).isEqualTo(totalRowCount2); + assertThat(orders2.size()).isLessThan(totalRowCount); + + + PagedList pagedList3 = Ebean.find(Order.class) + .setFirstRow(2) + .setMaxRows(150) + .findPagedList(); + + assertFalse(pagedList3.hasNext()); + assertTrue(pagedList3.hasPrev()); + + List list3 = pagedList3.getList(); + String xtoYofZ = pagedList3.getDisplayXtoYofZ(" to ", " of "); + assertThat(xtoYofZ).isEqualTo("3 to "+totalRowCount+" of "+totalRowCount); + assertThat(list3.size()).isEqualTo(totalRowCount - 2); + + PagedList pagedList4 = Ebean.find(Order.class) + .setFirstRow(0) + .setMaxRows(totalRowCount) + .findPagedList(); + + assertFalse(pagedList4.hasNext()); + assertFalse(pagedList4.hasPrev()); + assertThat(pagedList4.getDisplayXtoYofZ(" to ", "of ")).isEqualTo("1 to "+totalRowCount+" of "+totalRowCount); + + PagedList pagedList5 = Ebean.find(Order.class) + .setFirstRow(0) + .setMaxRows(totalRowCount - 1) + .findPagedList(); + + assertTrue(pagedList5.hasNext()); + assertFalse(pagedList5.hasPrev()); + } + @Test public void test_noCount() throws ExecutionException, InterruptedException {