Thanks Jacopo! I've begun converting the accounting component to use EntityQuery in r1628288, if anyone wants to help please feel free :-)
Regards Scott On 27/09/2014, at 9:59 pm, Jacopo Cappellato <jacopo.cappell...@hotwaxmedia.com> wrote: > This is really an amazing contribution! Thank you Scott. > > Jacopo > > On Sep 27, 2014, at 11:22 AM, lekt...@apache.org wrote: > >> Author: lektran >> Date: Sat Sep 27 09:22:31 2014 >> New Revision: 1627940 >> >> URL: http://svn.apache.org/r1627940 >> Log: >> OFBIZ-4053 Implement an entity query builder to be used as a friendlier API >> for executing entity queries. >> >> Entry point is the static EntityQuery.use(Delegator) method which will then >> return an EntityQuery instance whose methods support method chaining to set >> query options. >> The query can then be executed using the first(), list(), iterator() and >> one() methods which respectively return: >> - The first result from a result set >> - The full list of results from a result set >> - An EntityListIterator to iterate over a result set >> - The single record from a query that will return only one record (such as a >> lookup by primary key) >> >> Added: >> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> (with props) >> >> Added: >> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> URL: >> http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java?rev=1627940&view=auto >> ============================================================================== >> --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> (added) >> +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> Sat Sep 27 09:22:31 2014 >> @@ -0,0 +1,416 @@ >> +/******************************************************************************* >> + * Licensed to the Apache Software Foundation (ASF) under one >> + * or more contributor license agreements. See the NOTICE file >> + * distributed with this work for additional information >> + * regarding copyright ownership. The ASF licenses this file >> + * to you under the Apache License, Version 2.0 (the >> + * "License"); you may not use this file except in compliance >> + * with the License. You may obtain a copy of the License at >> + * >> + * http://www.apache.org/licenses/LICENSE-2.0 >> + * >> + * Unless required by applicable law or agreed to in writing, >> + * software distributed under the License is distributed on an >> + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY >> + * KIND, either express or implied. See the License for the >> + * specific language governing permissions and limitations >> + * under the License. >> + >> *******************************************************************************/ >> +package org.ofbiz.entity.util; >> + >> +import java.sql.Timestamp; >> +import java.util.Arrays; >> +import java.util.List; >> +import java.util.Map; >> +import java.util.Set; >> + >> +import org.ofbiz.base.util.Debug; >> +import org.ofbiz.base.util.UtilMisc; >> +import org.ofbiz.entity.Delegator; >> +import org.ofbiz.entity.GenericEntityException; >> +import org.ofbiz.entity.GenericValue; >> +import org.ofbiz.entity.condition.EntityCondition; >> +import org.ofbiz.entity.model.DynamicViewEntity; >> +import org.ofbiz.entity.util.EntityFindOptions; >> +import org.ofbiz.entity.util.EntityListIterator; >> +import org.ofbiz.entity.util.EntityUtil; >> + >> +/** >> + * Used to setup various options for and subsequently execute entity >> queries. >> + * >> + * All methods to set options modify the EntityQuery instance then return >> this modified object to allow method call chaining. It is >> + * important to note that this object is not immutable and is modified >> internally, and returning EntityQuery is just a >> + * self reference for convenience. >> + * >> + * After a query the object can be further modified and then used to >> perform another query if desired. >> + */ >> +public class EntityQuery { >> + >> + public static final String module = EntityQuery.class.getName(); >> + >> + private Delegator delegator; >> + private String entityName = null; >> + private DynamicViewEntity dynamicViewEntity = null; >> + private boolean useCache = false; >> + private EntityCondition whereEntityCondition = null; >> + private Set<String> fieldsToSelect = null; >> + private List<String> orderBy = null; >> + private Integer resultSetType = EntityFindOptions.TYPE_FORWARD_ONLY; >> + private Integer fetchSize = null; >> + private Integer maxRows = null; >> + private Boolean distinct = null; >> + private EntityCondition havingEntityCondition = null; >> + private boolean filterByDate = false; >> + private Timestamp filterByDateMoment; >> + >> + >> + >> + /** Construct an EntityQuery object for use against the specified >> Delegator >> + * @param delegator - The delegator instance to use for the query >> + * @return Returns a new EntityQuery object >> + */ >> + public static EntityQuery use(Delegator delegator) { >> + return new EntityQuery(delegator); >> + } >> + >> + /** Construct an EntityQuery object for use against the specified >> Delegator >> + * @param delegator - The delegator instance to use for the query >> + * @return Returns a new EntityQuery object >> + */ >> + public EntityQuery(Delegator delegator) { >> + this.delegator = delegator; >> + } >> + >> + /** Set the fields to be returned when the query is executed. >> + * >> + * Note that the select methods are not additive, if a subsequent >> + * call is made to select then the existing fields for selection >> + * will be replaced. >> + * @param fieldsToSelect - A Set of Strings containing the field names >> to be selected >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery select(Set<String> fieldsToSelect) { >> + this.fieldsToSelect = fieldsToSelect; >> + return this; >> + } >> + >> + /** Set the fields to be returned when the query is executed. >> + * >> + * Note that the select methods are not additive, if a subsequent >> + * call is made to select then the existing fields for selection >> + * will be replaced. >> + * @param fieldsToSelect - Strings containing the field names to be >> selected >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery select(String...fields) { >> + this.fieldsToSelect = UtilMisc.toSetArray(fields); >> + return this; >> + } >> + >> + /** Set the entity to query against >> + * @param entityName - The name of the entity to query against >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery from(String entityName) { >> + this.entityName = entityName; >> + this.dynamicViewEntity = null; >> + return this; >> + } >> + >> + /** Set the entity to query against >> + * @param dynamicViewEntity - The DynamicViewEntity object to query >> against >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery from(DynamicViewEntity dynamicViewEntity) { >> + this.dynamicViewEntity = dynamicViewEntity; >> + this.entityName = null; >> + return this; >> + } >> + >> + /** Set the EntityCondition to be used as the WHERE clause for the query >> + * >> + * NOTE: Each successive call to any of the where(...) methods will >> replace the currently set condition for the query. >> + * @param entityCondition - An EntityCondition object to be used as the >> where clause for this query >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery where(EntityCondition entityCondition) { >> + this.whereEntityCondition = entityCondition; >> + return this; >> + } >> + >> + /** Set a Map of field name/values to be ANDed together as the WHERE >> clause for the query >> + * >> + * NOTE: Each successive call to any of the where(...) methods will >> replace the currently set condition for the query. >> + * @param fieldMap - A Map of field names/values to be ANDed together >> as the where clause for the query >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery where(Map<String, Object> fieldMap) { >> + this.whereEntityCondition = EntityCondition.makeCondition(fieldMap); >> + return this; >> + } >> + >> + /** Set a series of field name/values to be ANDed together as the WHERE >> clause for the query >> + * >> + * NOTE: Each successive call to any of the where(...) methods will >> replace the currently set condition for the query. >> + * @param fieldMap - A series of field names/values to be ANDed >> together as the where clause for the query >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery where(Object...fields) { >> + this.whereEntityCondition = >> EntityCondition.makeCondition(UtilMisc.toMap(fields)); >> + return this; >> + } >> + >> + /** Set a list of EntityCondition objects to be ANDed together as the >> WHERE clause for the query >> + * >> + * NOTE: Each successive call to any of the where(...) methods will >> replace the currently set condition for the query. >> + * @param fieldMap - A list of EntityCondition objects to be ANDed >> together as the WHERE clause for the query >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery where(List<EntityCondition> andConditions) { >> + this.whereEntityCondition = >> EntityCondition.makeCondition(andConditions); >> + return this; >> + } >> + >> + /** Set the EntityCondition to be used as the HAVING clause for the >> query. >> + * >> + * NOTE: Each successive call to any of the having(...) methods will >> replace the currently set condition for the query. >> + * @param entityCondition - The EntityCondition object that specifies >> how to constrain >> + * this query after any groupings are done (if this is a view >> + * entity with group-by aliases) >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery having(EntityCondition entityCondition) { >> + this.havingEntityCondition = entityCondition; >> + return this; >> + } >> + >> + /** The fields of the named entity to order the resultset by; >> optionally add a " ASC" for ascending or " DESC" for descending >> + * >> + * NOTE: Each successive call to any of the orderBy(...) methods will >> replace the currently set orderBy fields for the query. >> + * @param orderBy - The fields of the named entity to order the >> resultset by >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery orderBy(List<String> orderBy) { >> + this.orderBy = orderBy; >> + return this; >> + } >> + >> + /** The fields of the named entity to order the resultset by; >> optionally add a " ASC" for ascending or " DESC" for descending >> + * >> + * NOTE: Each successive call to any of the orderBy(...) methods will >> replace the currently set orderBy fields for the query. >> + * @param orderBy - The fields of the named entity to order the >> resultset by >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery orderBy(String...fields) { >> + this.orderBy = Arrays.asList(fields); >> + return this; >> + } >> + >> + /** Indicate that the ResultSet object's cursor may move only forward >> (this is the default behavior) >> + * >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery cursorForwardOnly() { >> + this.resultSetType = EntityFindOptions.TYPE_FORWARD_ONLY; >> + return this; >> + } >> + >> + /** Indicate that the ResultSet object's cursor is scrollable but >> generally sensitive to changes to the data that underlies the ResultSet. >> + * >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery cursorScrollSensitive() { >> + this.resultSetType = EntityFindOptions.TYPE_SCROLL_SENSITIVE; >> + return this; >> + } >> + >> + /** Indicate that the ResultSet object's cursor is scrollable but >> generally not sensitive to changes to the data that underlies the ResultSet. >> + * >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery cursorScrollInsensitive() { >> + this.resultSetType = EntityFindOptions.TYPE_SCROLL_INSENSITIVE; >> + return this; >> + } >> + >> + /** Specifies the fetch size for this query. -1 will fall back to >> datasource settings. >> + * >> + * @param fetchSize - The fetch size for this query >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery fetchSize(int fetchSize) { >> + this.fetchSize = fetchSize; >> + return this; >> + } >> + >> + /** Specifies the max number of rows to return, 0 means all rows. >> + * >> + * @param maxRows - the max number of rows to return >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery maxRows(int maxRows) { >> + this.maxRows = maxRows; >> + return this; >> + } >> + >> + /** Specifies that the values returned should be filtered to remove >> duplicate values. >> + * >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery distinct() { >> + this.distinct = true; >> + return this; >> + } >> + >> + /** Specifies whether the values returned should be filtered to remove >> duplicate values. >> + * >> + * @param distinct - boolean indicating whether the values returned >> should be filtered to remove duplicate values >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery distinct(boolean distinct) { >> + this.distinct = distinct; >> + return this; >> + } >> + >> + /** Specifies whether results should be read from the cache (or written >> to the cache if the results have not yet been cached) >> + * >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery cache() { >> + this.useCache = true; >> + return this; >> + } >> + >> + /** Specifies whether the query should return only values that are >> currently active using from/thruDate fields. >> + * >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery filterByDate() { >> + this.filterByDate = true; >> + this.filterByDateMoment = null; >> + return this; >> + } >> + >> + /** Specifies whether the query should return only values that are >> active during the specified moment using from/thruDate fields. >> + * >> + * @param moment - Timestamp representing the moment in time that the >> values should be active during >> + * @return this EntityQuery object, to enable chaining >> + */ >> + public EntityQuery filterByDate(Timestamp moment) { >> + this.filterByDate = true; >> + this.filterByDateMoment = moment; >> + return this; >> + } >> + >> + /** Executes the EntityQuery and returns a list of results >> + * >> + * @return Returns a List of GenericValues representing the results of >> the query >> + */ >> + public List<GenericValue> queryList() throws GenericEntityException { >> + return query(null); >> + } >> + >> + /** Executes the EntityQuery and returns an EntityListIterator >> representing the result of the query. >> + * >> + * NOTE: THAT THIS MUST BE CLOSED (preferably in a finally block) WHEN >> YOU >> + * ARE DONE WITH IT, AND DON'T LEAVE IT OPEN TOO LONG BEACUSE IT >> + * WILL MAINTAIN A DATABASE CONNECTION. >> + * >> + * @return Returns an EntityListIterator representing the result of the >> query >> + */ >> + public EntityListIterator queryIterator() throws GenericEntityException >> { >> + if (useCache) { >> + Debug.logWarning("Call to iterator() with cache, ignoring >> cache", module); >> + } >> + if (dynamicViewEntity == null) { >> + return delegator.find(entityName, makeWhereCondition(false), >> havingEntityCondition, fieldsToSelect, orderBy, makeEntityFindOptions()); >> + } else { >> + return delegator.findListIteratorByCondition(dynamicViewEntity, >> makeWhereCondition(false), havingEntityCondition, fieldsToSelect, orderBy, >> makeEntityFindOptions()); >> + } >> + } >> + >> + /** Executes the EntityQuery and returns the first result >> + * >> + * @return GenericValue representing the first result record from the >> query >> + */ >> + public GenericValue queryFirst() throws GenericEntityException { >> + EntityFindOptions efo = makeEntityFindOptions(); >> + efo.setMaxRows(1); >> + GenericValue result = EntityUtil.getFirst(query(efo)); >> + return result; >> + } >> + >> + /** Executes the EntityQuery and a single result record >> + * >> + * @return GenericValue representing the only result record from the >> query >> + */ >> + public GenericValue queryOne() throws GenericEntityException { >> + GenericValue result = EntityUtil.getOnly(queryList()); >> + return result; >> + } >> + >> + /** Executes the EntityQuery and returns the result count >> + * >> + * If the query generates more than a single result then an exception >> is thrown >> + * >> + * @return GenericValue representing the only result record from the >> query >> + */ >> + public long queryCount() throws GenericEntityException { >> + if (dynamicViewEntity != null) { >> + return queryIterator().getResultsSizeAfterPartialList(); >> + } >> + return delegator.findCountByCondition(entityName, >> makeWhereCondition(false), havingEntityCondition, makeEntityFindOptions()); >> + } >> + >> + private List<GenericValue> query(EntityFindOptions efo) throws >> GenericEntityException { >> + EntityFindOptions findOptions = null; >> + if (efo == null) { >> + findOptions = makeEntityFindOptions(); >> + } else { >> + findOptions = efo; >> + } >> + List<GenericValue> result = null; >> + if (dynamicViewEntity == null) { >> + result = delegator.findList(entityName, >> makeWhereCondition(useCache), fieldsToSelect, orderBy, findOptions, >> useCache); >> + } else { >> + result = queryIterator().getCompleteList(); >> + } >> + if (filterByDate && useCache) { >> + if (filterByDateMoment == null) { >> + return EntityUtil.filterByDate(result); >> + } else { >> + return EntityUtil.filterByDate(result, filterByDateMoment); >> + } >> + } >> + return result; >> + } >> + >> + private EntityFindOptions makeEntityFindOptions() { >> + EntityFindOptions findOptions = new EntityFindOptions(); >> + if (resultSetType != null) { >> + findOptions.setResultSetType(resultSetType); >> + } >> + if (fetchSize != null) { >> + findOptions.setFetchSize(fetchSize); >> + } >> + if (maxRows != null) { >> + findOptions.setMaxRows(maxRows); >> + } >> + if (distinct != null) { >> + findOptions.setDistinct(distinct); >> + } >> + return findOptions; >> + } >> + >> + private EntityCondition makeWhereCondition(boolean usingCache) { >> + // we don't use the useCache field here because not all queries >> will actually use the cache, e.g. findCountByCondition never uses the cache >> + if (filterByDate && !usingCache) { >> + if (filterByDateMoment == null) { >> + return EntityCondition.makeCondition(whereEntityCondition, >> EntityUtil.getFilterByDateExpr()); >> + } else { >> + return EntityCondition.makeCondition(whereEntityCondition, >> EntityUtil.getFilterByDateExpr(filterByDateMoment)); >> + } >> + } >> + return whereEntityCondition; >> + } >> +} >> >> Propchange: >> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> ------------------------------------------------------------------------------ >> svn:eol-style = native >> >> Propchange: >> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> ------------------------------------------------------------------------------ >> svn:keywords = "Date Rev Author URL Id" >> >> Propchange: >> ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityQuery.java >> ------------------------------------------------------------------------------ >> svn:mime-type = text/plain >> >> >