#219 - Update PagedList javadoc - enable foreground query for getTotalRowCount() when loadRowCount() has not been used

This commit is contained in:
rbygrave
2014-12-03 22:33:35 +13:00
parent 9f71b0df22
commit 3a86be5f3d
5 changed files with 299 additions and 10 deletions
+27 -1
View File
@@ -89,6 +89,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -241,8 +248,27 @@
<doctitle>Ebean 4</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<!-- <excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames> -->
<additionalparam>-Xdoclint:none</additionalparam>
<!--<additionalparam>-Xdoclint:none</additionalparam>-->
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
<version>1.0.0</version>
</docletArtifact>
<additionalparam>
-Xdoclint:none
-include-basedir ${project.basedir}
-attributes "idseparator=-; project_name=${project.name}; \
project_version=${project.version}; \
project_desc=${project.description}"
</additionalparam>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
+115 -3
View File
@@ -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.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> 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<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <h4>Example: No total row count required</h4>
* <pre>{@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<Order> orders = pagedList.getList();
*
* }</pre>
*
* @param <T>
* the entity bean type
*
@@ -26,12 +63,53 @@ public interface PagedList<T> {
/**
* Initiate the loading of the total row count in the background.
* <pre>{@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<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <p>
* 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).
* </p>
*/
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.
* <p>
* The loadRowCount() & getTotalRowCount() methods internally make use of this getFutureRowCount() method.
* Generally I expect people to prefer loadRowCount() & getTotalRowCount() over getFutureRowCount().
* </p>
* <pre>{@code
*
* // initiate the row count query in the background thread
* Future<Integer> rowCount = pagedList.getFutureRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> 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();
*
* }</pre>
*/
public Future<Integer> getFutureRowCount();
@@ -42,11 +120,37 @@ public interface PagedList<T> {
/**
* Return the total row count for all pages.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <pre>{@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<Order> orders = pagedList.getList();
*
* // get the total row count (which was being executed
* // in a background thread if loadRowCount() was used)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public int getTotalRowCount();
/**
* Return the total number of pages based on the page size and total row count.
* <p>
* 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.
* </p>
*/
public int getTotalPageCount();
@@ -57,6 +161,10 @@ public interface PagedList<T> {
/**
* Return true if there is a next page.
* <p>
* 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.
* </p>
*/
public boolean hasNext();
@@ -68,7 +176,11 @@ public interface PagedList<T> {
/**
* 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.
*
* <p>
* 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.
* </p>
*
* @param to
* String to put between the first and last row
* @param of
@@ -28,6 +28,8 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
private final Monitor monitor = new Monitor();
private int foregroundTotalRowCount = -1;
private Future<Integer> futureRowCount;
private List<T> list;
@@ -51,13 +53,12 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
return futureRowCount;
}
}
public List<T> 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<T> implements PagedList<T> {
}
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;
}
}
@@ -0,0 +1,7 @@
package com.avaje.ebean.elasticsearch;
/**
* Created by rob on 2/12/14.
*/
public class TestBasicPush {
}
@@ -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<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
List<Order> orders = pagedList.getList();
assertTrue(!orders.isEmpty());
List<String> 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<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
Future<Integer> rowCount = pagedList.getFutureRowCount();
List<Order> 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<String> 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<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
pagedList.loadRowCount();
List<Order> orders = pagedList.getList();
int totalRowCount = pagedList.getTotalRowCount();
List<String> 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<Order> 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<Order> orders = pagedList.getList();
int totalRowCount = pagedList.getTotalRowCount();
// invoke it again but cached...
int totalRowCountAgain = pagedList.getTotalRowCount();
List<String> 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();
}
}
}