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.avajeavaje-agentloader
@@ -241,8 +248,27 @@
Ebean 4src/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();
+ }
+ }
+}