#410 - Require findPagedList() query to specify an order by clause - throw exception if no order by supplied

This commit is contained in:
Robin Bygrave
2015-09-14 11:29:38 +12:00
parent ec4f655312
commit 47829f3985
6 changed files with 179 additions and 80 deletions
+15 -2
View File
@@ -28,7 +28,7 @@ public final class OrderBy<T> implements Serializable {
* Create an empty OrderBy with no associated query.
*/
public OrderBy() {
this.list = new ArrayList<Property>(2);
this.list = new ArrayList<Property>(3);
}
private OrderBy(List<Property> list) {
@@ -52,7 +52,7 @@ public final class OrderBy<T> implements Serializable {
*/
public OrderBy(Query<T> query, String orderByClause) {
this.query = query;
this.list = new ArrayList<Property>(2);
this.list = new ArrayList<Property>(3);
parse(orderByClause);
}
@@ -83,6 +83,19 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Return true if the property is known to be contained in the order by clause.
*/
public boolean containsProperty(String propertyName) {
for (int i = 0; i < list.size(); i++) {
if (propertyName.equals(list.get(i).getProperty())) {
return true;
}
}
return false;
}
/**
* Return a copy of this OrderBy with the path trimmed.
*/
@@ -1363,14 +1363,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize) {
SpiQuery spiQuery = (SpiQuery<T>)query;
OrderBy orderBy = spiQuery.getOrderBy();
if (orderBy == null || orderBy.isEmpty()) {
// add a default order by for paging queries
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
query.orderBy(desc.getDefaultOrderBy());
}
return new LimitOffsetPagedList<T>(this, spiQuery, pageIndex, pageSize);
return new LimitOffsetPagedList<T>(this, (SpiQuery<T>)query, pageIndex, pageSize);
}
public <T> void findEach(Query<T> query, QueryEachConsumer<T> consumer, Transaction t) {
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.ValuePair;
@@ -2098,6 +2099,26 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
}
/**
* Appends the Id property to the OrderBy clause if it is not believed
* to be already contained in the order by.
* <p>
* This is primarily used for paging queries to ensure that an order by clause is provided and that the order by
* provides unique ordering of the rows (so that the paging is predicable).
* </p>
*/
public void appendOrderById(SpiQuery<T> query) {
if (idProperty != null) {
OrderBy<T> orderBy = query.getOrderBy();
if (orderBy == null || orderBy.isEmpty()) {
query.order().asc(idProperty.getName());
} else if (!orderBy.containsProperty(idProperty.getName())){
query.order().asc(idProperty.getName());
}
}
}
/**
* All the BeanPropertyAssocOne that are not embedded. These are effectively
* joined beans. For ManyToOne and OneToOne associations.
@@ -277,6 +277,14 @@ public class CQueryEngine {
*/
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
if (query.getMaxRows() > 1 || query.getFirstRow() > 0) {
// deemed to be a be a paging query - check that the order by contains
// the id property to ensure unique row ordering for predicable paging
request.getBeanDescriptor().appendOrderById(query);
}
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
@@ -293,7 +301,7 @@ public class CQueryEngine {
BeanCollection<T> beanCollection = cquery.readCollection();
BeanCollectionTouched collectionTouched = request.getQuery().getBeanCollectionTouched();
BeanCollectionTouched collectionTouched = query.getBeanCollectionTouched();
if (collectionTouched != null) {
// register a listener that wants to be notified when the
// bean collection is first used
@@ -319,7 +327,7 @@ public class CQueryEngine {
if (cquery != null) {
cquery.close();
}
if (request.getQuery().isFutureFetch()) {
if (query.isFutureFetch()) {
// end the transaction for futureFindIds
// as it had it's own transaction
logger.debug("Future fetch completed!");