From 41f3173adfa4c52ccb7c83cd16b5055b0dca900e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 16 May 2014 01:41:56 +1200 Subject: [PATCH] Add PagedList was part of #96 --- .../java/com/avaje/ebean/EbeanServer.java | 25 +++- .../java/com/avaje/ebean/ExpressionList.java | 26 ++++- src/main/java/com/avaje/ebean/PagedList.java | 80 +++++++++++++ src/main/java/com/avaje/ebean/Query.java | 40 ++++--- .../server/core/DefaultServer.java | 8 ++ .../server/expression/JunctionExpression.java | 6 + .../server/query/LimitOffsetPagedList.java | 110 ++++++++++++++++++ .../server/querydefn/DefaultOrmQuery.java | 8 +- .../util/DefaultExpressionList.java | 6 + .../avaje/tests/basic/TestExplicitInsert.java | 27 +---- .../tests/query/other/TestFindPagedList.java | 76 ++++++++++++ 11 files changed, 367 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/PagedList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java create mode 100644 src/test/java/com/avaje/tests/query/other/TestFindPagedList.java diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index e7848d7cc..aeac5f9c6 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -547,10 +547,32 @@ public interface EbeanServer { /** * Find using a PagingList with explicit transaction and pageSize. - * @deprecated + * Deprecated in favour of findPagedList(). + * @deprecated */ public PagingList findPagingList(Query query, Transaction transaction, int pageSize); + /** + * Return a PagedList for this query. + *

+ * 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 + * {@link Query#findFutureRowCount()} to determine total row count, total page count etc. + *

+ *

+ * Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on + * the query. This translates into SQL that uses limit offset, rownum or row_number + * function to limit the result set. + *

+ * + * @param pageIndex + * The zero based index of the page. + * @param pageSize + * The number of beans to return per page. + * @return The PagedList + */ + public PagedList findPagedList(Query query, Transaction transaction, int pageIndex, int pageSize); + /** * Execute the query returning a set of entity beans. *

@@ -1079,4 +1101,5 @@ public interface EbeanServer { */ public JsonContext createJsonContext(); + } diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index bd0ae473b..3a06bc9ab 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -200,10 +200,34 @@ public interface ExpressionList extends Serializable { * * @param pageSize * the number of beans fetched per Page - * + * @deprecated */ public PagingList findPagingList(int pageSize); + /** + * Return a PagedList for this query. + *

+ * 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 + * {@link Query#findFutureRowCount()} to determine total row count, total page count etc. + *

+ *

+ * Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on + * the query. This translates into SQL that uses limit offset, rownum or row_number + * function to limit the result set. + *

+ * + * @param pageIndex + * The zero based index of the page. + * @param pageSize + * The number of beans to return per page. + * @return The PagedList + */ + public PagedList findPagedList(int pageIndex, int pageSize); + + /** + * Add some filter predicate expressions to the many property. + */ public ExpressionList filterMany(String prop); /** diff --git a/src/main/java/com/avaje/ebean/PagedList.java b/src/main/java/com/avaje/ebean/PagedList.java new file mode 100644 index 000000000..d273b4c9b --- /dev/null +++ b/src/main/java/com/avaje/ebean/PagedList.java @@ -0,0 +1,80 @@ +package com.avaje.ebean; + +import java.util.List; +import java.util.concurrent.Future; + +/** + * Represents a page of results. + *

+ * The benefit of using PagedList over just using the normal Query with + * {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} is that it additionally wraps + * functionality that can call {@link Query#findFutureRowCount()} to determine total row count, + * total page count etc. + *

+ *

+ * Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on + * the query. This translates into SQL that uses limit offset, rownum or row_number function to + * limit the result set. + *

+ * + * @param + * the entity bean type + * + * @see Query#findPagedList(int, int) + */ +public interface PagedList { + + /** + * Initiate the loading of the total row count in the background. + */ + 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. + */ + public Future getFutureRowCount(); + + /** + * Return the list of entities for this page. + */ + public List getList(); + + /** + * Return the total row count for all pages. + */ + public int getTotalRowCount(); + + /** + * Return the total number of pages based on the page size and total row count. + */ + public int getTotalPageCount(); + + /** + * Return the index position of this page. Zero based. + */ + public int getPageIndex(); + + /** + * Return true if there is a next page. + */ + public boolean hasNext(); + + /** + * Return true if there is a previous page. + */ + public boolean hasPrev(); + + /** + * 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. + * + * @param to + * String to put between the first and last row + * @param of + * String to put between the last row and the total row count + * + * @return String of the format XtoYofZ. + */ + public String getDisplayXtoYofZ(String to, String of); +} diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index 9cc229fb9..ab7a1bfbe 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -589,9 +589,7 @@ public interface Query extends Serializable { /** * Execute find list query in a background thread. *

- * This returns a Future object which can be used to cancel, check the - * execution status (isDone etc) and get the value (with or without a - * timeout). + * Deprecated with a view to simplifying internals. *

* * @return a Future object for the list result of the query @@ -600,25 +598,33 @@ public interface Query extends Serializable { public FutureList findFutureList(); /** - * Return a PagingList for this query. - *

- * This can be used to break up a query into multiple queries to fetch the - * data a page at a time. - *

- *

- * This typically works by using a query per page and setting - * {@link Query#setFirstRow(int)} and and {@link Query#setMaxRows(int)} on the - * query. This usually would translate into SQL that uses limit offset, rownum - * or row_number function to limit the result set. - *

- * - * @param pageSize - * the number of beans fetched per Page + * This is being deprecated in favour of the simplier {@link Query#findPagedList(int, int)}. * * @deprecated */ public PagingList findPagingList(int pageSize); + /** + * Return a PagedList for this query. + *

+ * 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 + * {@link Query#findFutureRowCount()} to determine total row count, total page count etc. + *

+ *

+ * Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on + * the query. This translates into SQL that uses limit offset, rownum or row_number function to + * limit the result set. + *

+ * + * @param pageIndex + * The zero based index of the page. + * @param pageSize + * The number of beans to return per page. + * @return The PagedList + */ + public PagedList findPagedList(int pageIndex, int pageSize); + /** * 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 3ba968eda..5e2af4eb4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -30,6 +30,7 @@ import com.avaje.ebean.Filter; import com.avaje.ebean.FutureIds; import com.avaje.ebean.FutureList; import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.PagedList; import com.avaje.ebean.PagingList; import com.avaje.ebean.Query; import com.avaje.ebean.QueryIterator; @@ -96,6 +97,7 @@ import com.avaje.ebeaninternal.server.query.CallableQueryIds; import com.avaje.ebeaninternal.server.query.CallableQueryList; import com.avaje.ebeaninternal.server.query.CallableQueryRowCount; import com.avaje.ebeaninternal.server.query.CallableSqlQueryList; +import com.avaje.ebeaninternal.server.query.LimitOffsetPagedList; import com.avaje.ebeaninternal.server.query.LimitOffsetPagingQuery; import com.avaje.ebeaninternal.server.query.QueryFutureIds; import com.avaje.ebeaninternal.server.query.QueryFutureList; @@ -1419,6 +1421,12 @@ public final class DefaultServer implements SpiEbeanServer { return new LimitOffsetPagingQuery(this, spiQuery, pageSize); } + @Override + public PagedList findPagedList(Query query, Transaction transaction, int pageIndex, int pageSize) { + + return new LimitOffsetPagedList(this, (SpiQuery)query, pageIndex, pageSize); + } + public void findVisit(Query query, QueryResultVisitor visitor, Transaction t) { SpiOrmQueryRequest request = createQueryRequest(Type.LIST, 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 c3062bac5..c55eac335 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -12,6 +12,7 @@ import com.avaje.ebean.FutureList; import com.avaje.ebean.FutureRowCount; import com.avaje.ebean.Junction; import com.avaje.ebean.OrderBy; +import com.avaje.ebean.PagedList; import com.avaje.ebean.PagingList; import com.avaje.ebean.QueryIterator; import com.avaje.ebean.QueryResultVisitor; @@ -229,6 +230,11 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr public PagingList findPagingList(int pageSize) { return exprList.findPagingList(pageSize); } + + @Override + public PagedList findPagedList(int pageIndex, int pageSize) { + return exprList.findPagedList(pageIndex, pageSize); + } 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 new file mode 100644 index 000000000..994f4d84b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java @@ -0,0 +1,110 @@ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Future; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.PagedList; +import com.avaje.ebeaninternal.api.Monitor; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * PagedList implementation based on limit offset types of queries. + * + * @param + * the entity bean type + */ +public class LimitOffsetPagedList implements PagedList { + + private final transient EbeanServer server; + + private final SpiQuery query; + + private final int pageSize; + + private final int pageIndex; + + private final Monitor monitor = new Monitor(); + + private Future futureRowCount; + + private List list; + + public LimitOffsetPagedList(EbeanServer server, SpiQuery query, int pageIndex, int pageSize) { + this.server = server; + this.query = query; + this.pageSize = pageSize; + this.pageIndex = pageIndex; + } + + public void loadRowCount() { + getFutureRowCount(); + } + + public Future getFutureRowCount() { + synchronized (monitor) { + if (futureRowCount == null) { + futureRowCount = server.findFutureRowCount(query, null); + } + return futureRowCount; + } + } + + public List getList() { + synchronized (monitor) { + if (list == null) { + query.setFirstRow(pageIndex * pageSize); + query.setMaxRows(pageSize); + + list = server.findList(query, null); + } + return list; + } + } + + public int getTotalPageCount() { + + int rowCount = getTotalRowCount(); + if (rowCount == 0) { + return 0; + } else { + return ((rowCount - 1) / pageSize) + 1; + } + } + + public int getTotalRowCount() { + try { + return getFutureRowCount().get(); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + + public boolean hasNext() { + return pageIndex < (getTotalPageCount() - 1); + } + + public boolean hasPrev() { + return pageIndex > 0; + } + + public int getPageIndex() { + return pageIndex; + } + + public int getPageSize() { + return pageSize; + } + + public String getDisplayXtoYofZ(String to, String of) { + + int first = pageIndex * pageSize + 1; + int last = first + getList().size() - 1; + 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 e0e0e39e8..c4062befe 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -18,6 +18,7 @@ import com.avaje.ebean.FutureList; import com.avaje.ebean.FutureRowCount; import com.avaje.ebean.OrderBy; import com.avaje.ebean.OrderBy.Property; +import com.avaje.ebean.PagedList; import com.avaje.ebean.PagingList; import com.avaje.ebean.Query; import com.avaje.ebean.QueryIterator; @@ -915,7 +916,12 @@ public class DefaultOrmQuery implements SpiQuery { return server.findPagingList(this, null, pageSize); } - /** + @Override + public PagedList findPagedList(int pageIndex, int pageSize) { + return server.findPagedList(this, null, pageIndex, pageSize); + } + + /** * 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 have in the query. diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 0323837fa..267dea69e 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -14,6 +14,7 @@ import com.avaje.ebean.FutureList; import com.avaje.ebean.FutureRowCount; import com.avaje.ebean.Junction; import com.avaje.ebean.OrderBy; +import com.avaje.ebean.PagedList; import com.avaje.ebean.PagingList; import com.avaje.ebean.Query; import com.avaje.ebean.QueryIterator; @@ -152,6 +153,11 @@ public class DefaultExpressionList implements SpiExpressionList { return query.findPagingList(pageSize); } + @Override + public PagedList findPagedList(int pageIndex, int pageSize) { + return query.findPagedList(pageIndex, pageSize); + } + public int findRowCount() { return query.findRowCount(); } diff --git a/src/test/java/com/avaje/tests/basic/TestExplicitInsert.java b/src/test/java/com/avaje/tests/basic/TestExplicitInsert.java index 1a9655fc6..cab07c774 100644 --- a/src/test/java/com/avaje/tests/basic/TestExplicitInsert.java +++ b/src/test/java/com/avaje/tests/basic/TestExplicitInsert.java @@ -8,7 +8,6 @@ import org.junit.Test; import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.Update; import com.avaje.tests.model.basic.EBasic; public class TestExplicitInsert extends BaseTestCase { @@ -16,9 +15,6 @@ public class TestExplicitInsert extends BaseTestCase { @Test public void test() { - // GlobalProperties.put("ebean.classes", - // ""+LDPerson.class.toString()+","+EBasic.class.toString()); - EBasic b = new EBasic(); b.setName("exp insert"); b.setDescription("explicit insert"); @@ -38,28 +34,9 @@ public class TestExplicitInsert extends BaseTestCase { Assert.assertNotNull(b2.getId()); Assert.assertTrue(!b.getId().equals(b2.getId())); - List list = server.find(EBasic.class).setMaxRows(10).findList(); + List list = server.find(EBasic.class).where().in("id",b.getId(), b2.getId()).findList(); - Assert.assertTrue(list.size() >= 2); - - int firstRow = 1; - List list2 = server.find(EBasic.class).order().asc("id").setFirstRow(firstRow) - .setMaxRows(10).findList(); - - int expectedCount = list.size() - firstRow; - if (expectedCount > 0) { - Assert.assertEquals(expectedCount, list2.size()); - } else { - Assert.assertTrue(list2.isEmpty()); - } - - Update update = Ebean.createUpdate(EBasic.class, - "update ebasic set description = 'test'"); - - int rows = update.execute(); - - Assert.assertTrue(rows > 0); - Ebean.externalModification("e_basic", true, false, true); + Assert.assertEquals(2, list.size()); } } diff --git a/src/test/java/com/avaje/tests/query/other/TestFindPagedList.java b/src/test/java/com/avaje/tests/query/other/TestFindPagedList.java new file mode 100644 index 000000000..e6a1de7c1 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestFindPagedList.java @@ -0,0 +1,76 @@ +package com.avaje.tests.query.other; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.junit.Assert; +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.PagedList; +import com.avaje.ebean.Transaction; +import com.avaje.tests.model.basic.EBasic; + +public class TestFindPagedList extends BaseTestCase { + + @Test + public void test() throws InterruptedException, ExecutionException { + + + EbeanServer server = Ebean.getServer(null); + + Transaction transaction = server.beginTransaction(); + try { + transaction.setBatchMode(true); + transaction.setBatchSize(20); + for (int i = 0; i < 87; i++) { + EBasic dumbModel = new EBasic(); + dumbModel.setName("HelloB0Bi"); + server.save(dumbModel); + } + transaction.commit(); + + } finally { + transaction.end(); + } + + PagedList list1 = Ebean.find(EBasic.class) + .where().like("name", "HelloB0B%") + .findPagedList(0, 10); + + list1.loadRowCount(); + List list = list1.getList(); + int totalRowCount = list1.getTotalRowCount(); + int totalPageCount = list1.getTotalPageCount(); + + Assert.assertEquals(10, list.size()); + Assert.assertEquals(87, totalRowCount); + Assert.assertEquals(9, totalPageCount); + + PagedList list2 = Ebean.find(EBasic.class) + .where().like("name", "HelloB0B%") + .findPagedList(4, 10); + + list = list2.getList(); + + Assert.assertEquals(10, list2.getList().size()); + Assert.assertEquals(87, list2.getTotalRowCount()); + Assert.assertEquals(9, list2.getTotalPageCount()); + + PagedList list3 = Ebean.find(EBasic.class) + .where().like("name", "HelloB0B%") + .findPagedList(8, 10); + + Future rowCount = list3.getFutureRowCount(); + list = list3.getList(); + + Assert.assertEquals(Integer.valueOf(87), rowCount.get()); + Assert.assertEquals(7, list3.getList().size()); + Assert.assertEquals(87, list3.getTotalRowCount()); + Assert.assertEquals(9, list3.getTotalPageCount()); + } + +}