From 3a86be5f3d52bc405a0d9ac6b81e22da0f45da57 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 3 Dec 2014 22:33:35 +1300 Subject: [PATCH] #219 - Update PagedList javadoc - enable foreground query for getTotalRowCount() when loadRowCount() has not been used --- pom.xml | 28 +++- src/main/java/com/avaje/ebean/PagedList.java | 118 +++++++++++++++- .../server/query/LimitOffsetPagedList.java | 24 +++- .../ebean/elasticsearch/TestBasicPush.java | 7 + .../tests/query/TestQueryFindPagedList.java | 132 ++++++++++++++++++ 5 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/avaje/ebean/elasticsearch/TestBasicPush.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java diff --git a/pom.xml b/pom.xml index a998a6bb4..31565e8ad 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,13 @@ provided + + com.squareup.okhttp + okhttp + 2.1.0 + test + + org.avaje avaje-agentloader @@ -241,8 +248,27 @@ Ebean 4 src/main/java/com/avaje/ebean/overview.html - -Xdoclint:none + + 1.8 + org.avaje.doclet.PygmentsDoclet + com.avaje.ebeaninternal.*:com.avaje.ebean.util + + org.avaje + pygments-doclet + 1.0.0 + + + -Xdoclint:none + -include-basedir ${project.basedir} + -attributes "idseparator=-; project_name=${project.name}; \ + project_version=${project.version}; \ + project_desc=${project.description}" + + true + src/main/java/com/avaje/ebean/overview.html + + attach-javadocs diff --git a/src/main/java/com/avaje/ebean/PagedList.java b/src/main/java/com/avaje/ebean/PagedList.java index d273b4c9b..e9e096c40 100644 --- a/src/main/java/com/avaje/ebean/PagedList.java +++ b/src/main/java/com/avaje/ebean/PagedList.java @@ -16,7 +16,44 @@ import java.util.concurrent.Future; * the query. This translates into SQL that uses limit offset, rownum or row_number function to * limit the result set. *

- * + * + *

Example: typical use including total row count

+ *
{@code
+ *
+ *     // We want to find the first 100 new orders
+ *     //  ... 0 means first page
+ *     //  ... page size is 100
+ *
+ *     PagedList pagedList
+ *       = ebeanServer.find(Order.class)
+ *       .where().eq("status", Order.Status.NEW)
+ *       .order().asc("id")
+ *       .findPagedList(0, 100);
+ *
+ *     // 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: No total row count required

+ *
{@code
+ *
+ *     // If you are not getting the 'first page' often
+ *     // you do not bother getting the total row count again
+ *     // so instead just get the page list of data
+ *
+ *     // fetch and return the list in the foreground thread
+ *     List orders = pagedList.getList();
+ *
+ * }
+ * * @param * the entity bean type * @@ -26,12 +63,53 @@ public interface PagedList { /** * Initiate the loading of the total row count in the background. + *
{@code
+   *
+   *     // 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();
+   *
+   * }
+ * + *

+ * Also note that using loadRowCount() and getTotalRowCount() rather than getFutureRowCount() + * means that exceptions ExecutionException, InterruptedException, TimeoutException are instead + * wrapped in the unchecked PersistenceException (which might be preferrable). + *

*/ public void loadRowCount(); /** * Return the Future row count. You might get this if you wish to cancel the total row count query - * or specify a timeout for that query. + * or specify a timeout for the row count query. + *

+ * The loadRowCount() & getTotalRowCount() methods internally make use of this getFutureRowCount() method. + * Generally I expect people to prefer loadRowCount() & getTotalRowCount() over getFutureRowCount(). + *

+ *
{@code
+   *
+   *     // initiate the row count query in the background thread
+   *     Future rowCount = pagedList.getFutureRowCount();
+   *
+   *     // fetch and return the list in the foreground thread
+   *     List orders = pagedList.getList();
+   *
+   *     // now get the total count with a timeout
+   *     Integer totalRowCount = rowCount.get(30, TimeUnit.SECONDS);
+   *
+   *     // or ge the total count without a timeout
+   *     Integer totalRowCountViaFuture = rowCount.get();
+   *
+   *     // which is actually the same as ...
+   *     int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
*/ public Future getFutureRowCount(); @@ -42,11 +120,37 @@ public interface PagedList { /** * Return the total row count for all pages. + *

+ * If loadRowCount() has already been called then the row count query is already executing in a background thread + * and this gets the associated Future and gets the value waiting for the future to finish. + *

+ *

+ * If loadRowCount() has not been called then this executes the find row count query and returns the result and this + * will just occur in the current thread and not use a background thread. + *

+ *
{@code
+   *
+   *     // 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 (which was being executed
+   *     // in a background thread if loadRowCount() was used)
+   *     int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
*/ public int getTotalRowCount(); /** * Return the total number of pages based on the page size and total row count. + *

+ * This method requires that the total row count has been fetched and will invoke + * the total row count query if it has not already been invoked. + *

*/ public int getTotalPageCount(); @@ -57,6 +161,10 @@ public interface PagedList { /** * Return true if there is a next page. + *

+ * This method requires that the total row count has been fetched and will invoke + * the total row count query if it has not already been invoked. + *

*/ public boolean hasNext(); @@ -68,7 +176,11 @@ public interface PagedList { /** * Helper method to return a "X to Y of Z" string for this page where X is the first row, Y the * last row and Z the total row count. - * + *

+ * This method requires that the total row count has been fetched and will invoke + * the total row count query if it has not already been invoked. + *

+ * * @param to * String to put between the first and last row * @param of 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 994f4d84b..12e9dddce 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java @@ -28,6 +28,8 @@ public class LimitOffsetPagedList implements PagedList { private final Monitor monitor = new Monitor(); + private int foregroundTotalRowCount = -1; + private Future futureRowCount; private List list; @@ -51,13 +53,12 @@ public class LimitOffsetPagedList implements PagedList { return futureRowCount; } } - + public List getList() { synchronized (monitor) { if (list == null) { query.setFirstRow(pageIndex * pageSize); query.setMaxRows(pageSize); - list = server.findList(query, null); } return list; @@ -75,10 +76,21 @@ public class LimitOffsetPagedList implements PagedList { } public int getTotalRowCount() { - try { - return getFutureRowCount().get(); - } catch (Exception e) { - throw new PersistenceException(e); + synchronized (monitor) { + if (futureRowCount != null) { + try { + // background query already initiated so get it with a wait + return futureRowCount.get(); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + // already fetched? + if (foregroundTotalRowCount > -1) return foregroundTotalRowCount; + + // just using foreground thread + foregroundTotalRowCount = server.findRowCount(query, null); + return foregroundTotalRowCount; } } diff --git a/src/test/java/com/avaje/ebean/elasticsearch/TestBasicPush.java b/src/test/java/com/avaje/ebean/elasticsearch/TestBasicPush.java new file mode 100644 index 000000000..97efdb90b --- /dev/null +++ b/src/test/java/com/avaje/ebean/elasticsearch/TestBasicPush.java @@ -0,0 +1,7 @@ +package com.avaje.ebean.elasticsearch; + +/** + * Created by rob on 2/12/14. + */ +public class TestBasicPush { +} diff --git a/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java b/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java new file mode 100644 index 000000000..808f4e34d --- /dev/null +++ b/src/test/java/com/avaje/tests/query/TestQueryFindPagedList.java @@ -0,0 +1,132 @@ +package com.avaje.tests.query; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.PagedList; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.ResetBasicData; +import org.avaje.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class TestQueryFindPagedList extends BaseTestCase { + + @Test + public void test_noCount() throws ExecutionException, InterruptedException { + + ResetBasicData.reset(); + + PagedList pagedList = Ebean.find(Order.class).findPagedList(0, 3); + + 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_countInBackground() throws ExecutionException, InterruptedException, TimeoutException { + + ResetBasicData.reset(); + + PagedList pagedList = Ebean.find(Order.class).findPagedList(0, 3); + + LoggedSqlCollector.start(); + + 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(); + + List loggedSql = LoggedSqlCollector.stop(); + + assertTrue(orders.size() < totalRowCount); + assertEquals(Integer.valueOf(totalRowCount), totalRowCountViaFuture); + assertEquals(Integer.valueOf(totalRowCount), totalRowCountWithTimeout); + assertEquals(2, loggedSql.size()); + + String firstTxn = loggedSql.get(0).substring(0, 10); + String secTxn = loggedSql.get(1).substring(0, 10); + + assertNotEquals(firstTxn, secTxn); + } + + + @Test + public void test_countInBackground_withLoadRowCount() { + + ResetBasicData.reset(); + + PagedList pagedList = Ebean.find(Order.class).findPagedList(0, 3); + + LoggedSqlCollector.start(); + + pagedList.loadRowCount(); + List orders = pagedList.getList(); + int totalRowCount = pagedList.getTotalRowCount(); + + List loggedSql = LoggedSqlCollector.stop(); + + assertTrue(orders.size() < totalRowCount); + assertEquals(2, loggedSql.size()); + + String firstTxn = loggedSql.get(0).substring(0, 10); + String secTxn = loggedSql.get(1).substring(0, 10); + + assertNotEquals(firstTxn, secTxn); + } + + + @Test + public void test_countUsingForegound() throws ExecutionException, InterruptedException { + + ResetBasicData.reset(); + + PagedList pagedList = Ebean.find(Order.class).findPagedList(0, 3); + + LoggedSqlCollector.start(); + + // kinda not normal but just wrap in a transaction to assert + // the background fetch does not occur (which explicitly creates + // its own transaction) ... so a bit naughty with the test here + Ebean.beginTransaction(); + try { + + List orders = pagedList.getList(); + int totalRowCount = pagedList.getTotalRowCount(); + + // invoke it again but cached... + int totalRowCountAgain = pagedList.getTotalRowCount(); + + List loggedSql = LoggedSqlCollector.stop(); + + assertTrue(orders.size() < totalRowCount); + assertEquals(2, loggedSql.size()); + assertEquals(totalRowCount, totalRowCountAgain); + + String firstTxn = loggedSql.get(0).substring(0, 10); + String secTxn = loggedSql.get(1).substring(0, 10); + + assertEquals(firstTxn, secTxn); + + } finally { + Ebean.endTransaction(); + } + } +}