📄 largeselect.java
字号:
package org.apache.torque.util;/* * Copyright 2001-2004 The Apache Software Foundation. * * Licensed 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. */import java.sql.Connection;import java.sql.SQLException;import java.util.Iterator;import java.util.List;import java.util.ArrayList;import java.util.Hashtable;import java.util.Set;import java.io.Serializable;import java.lang.reflect.Method;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.torque.Torque;import org.apache.torque.TorqueException;import com.workingdogs.village.QueryDataSet;import com.workingdogs.village.DataSetException;/** * This class can be used to retrieve a large result set from a database query. * The query is started and then rows are returned a page at a time. The <code> * LargeSelect</code> is meant to be placed into the Session or User.Temp, so * that it can be used in response to several related requests. Note that in * order to use <code>LargeSelect</code> you need to be willing to accept the * fact that the result set may become inconsistent with the database if updates * are processed subsequent to the queries being executed. Specifying a memory * page limit of 1 will give you a consistent view of the records but the totals * may not be accurate and the performance will be terrible. In most cases * the potential for inconsistencies data should not cause any serious problems * and performance should be pretty good (but read on for further warnings). * * <p>The idea here is that the full query result would consume too much memory * and if displayed to a user the page would be too long to be useful. Rather * than loading the full result set into memory, a window of data (the memory * limit) is loaded and retrieved a page at a time. If a request occurs for * data that falls outside the currently loaded window of data then a new query * is executed to fetch the required data. Performance is optimized by * starting a thread to execute the database query and fetch the results. This * will perform best when paging forwards through the data, but a minor * optimization where the window is moved backwards by two rather than one page * is included for when a user pages past the beginning of the window. * * <p>As the query is performed in in steps, it is often the case that the total * number of records and pages of data is unknown. <code>LargeSelect</code> * provides various methods for indicating how many records and pages it is * currently aware of and for presenting this information to users. * * <p><code>LargeSelect</code> utilises the <code>Criteria</code> methods * <code>setOffset()</code> and <code>setLimit()</code> to limit the amount of * data retrieved from the database - these values are either passed through to * the DBMS when supported (efficient with the caveat below) or handled by * the Village API when it is not (not so efficient). At time of writing * <code>Criteria</code> will only pass the offset and limit through to MySQL * and PostgreSQL (with a few changes to <code>DBOracle</code> and <code> * BasePeer</code> Oracle support can be implemented by utilising the <code> * rownum</code> pseudo column). * * <p>As <code>LargeSelect</code> must re-execute the query each time the user * pages out of the window of loaded data, you should consider the impact of * non-index sort orderings and other criteria that will require the DBMS to * execute the entire query before filtering down to the offset and limit either * internally or via Village. * * <p>The memory limit defaults to 5 times the page size you specify, but * alternative constructors and the class method <code>setMemoryPageLimit() * </code> allow you to override this for a specific instance of * <code>LargeSelect</code> or future instances respectively. * * <p>Some of the constructors allow you to specify the name of the class to use * to build the returnd rows. This works by using reflection to find <code> * addSelectColumns(Criteria)</code> and <code>populateObjects(List)</code> * methods to add the necessary select columns to the criteria (only if it * doesn't already contain any) and to convert query results from Village * <code>Record</code> objects to a class defined within the builder class. * This allows you to use any of the Torque generated Peer classes, but also * makes it fairly simple to construct business object classes that can be used * for this purpose (simply copy and customise the <code>addSelectColumns() * </code>, <code>populateObjects()</code>, <code>row2Object()</code> and <code> * populateObject()</code> methods from an existing Peer class). * * <p>Typically you will create a <code>LargeSelect</code> using your <code> * Criteria</code> (perhaps created from the results of a search parameter * page), page size, memory page limit and return class name (for which you may * have defined a business object class before hand) and place this in user.Temp * thus: * * <pre> * data.getUser().setTemp("someName", largeSelect); * </pre> * * <p>In your template you will then use something along the lines of: * * <pre> * #set ($largeSelect = $data.User.getTemp("someName")) * #set ($searchop = $data.Parameters.getString("searchop")) * #if ($searchop.equals("prev")) * #set ($recs = $largeSelect.PreviousResults) * #else * #if ($searchop.equals("goto")) * #set ($recs * = $largeSelect.getPage($data.Parameters.getInt("page", 1))) * #else * #set ($recs = $largeSelect.NextResults) * #end * #end * </pre> * * <p>...to move through the records. <code>LargeSelect</code> implements a * number of convenience methods that make it easy to add all of the necessary * bells and whistles to your template. * * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a> * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a> * @version $Id: LargeSelect.java,v 1.16 2005/02/16 07:58:49 tfischer Exp $ */public class LargeSelect implements Runnable, Serializable{ /** The number of records that a page consists of. */ private int pageSize; /** The maximum number of records to maintain in memory. */ private int memoryLimit; /** The record number of the first record in memory. */ private int blockBegin = 0; /** The record number of the last record in memory. */ private int blockEnd; /** How much of the memory block is currently occupied with result data. */ private volatile int currentlyFilledTo = -1; /** The SQL query that this <code>LargeSelect</code> represents. */ private String query; /** The database name to get from Torque. */ private String dbName; /** Used to retrieve query results from Village. */ private QueryDataSet qds = null; /** The memory store of records. */ private List results = null; /** The thread that executes the query. */ private Thread thread = null; /** * A flag used to kill the thread when the currently executing query is no * longer required. */ private volatile boolean killThread = false; /** A flag that indicates whether or not the query thread is running. */ private volatile boolean threadRunning = false; /** * An indication of whether or not the current query has completed * processing. */ private volatile boolean queryCompleted = false; /** * An indication of whether or not the totals (records and pages) are at * their final values. */ private boolean totalsFinalized = false; /** The cursor position in the result set. */ private int position; /** The total number of pages known to exist. */ private int totalPages = -1; /** The total number of records known to exist. */ private int totalRecords = 0; /** The number of the page that was last retrieved. */ private int currentPageNumber = 0; /** The criteria used for the query. */ private Criteria criteria = null; /** The last page of results that were returned. */ private List lastResults; /** * The class that is possibly used to construct the criteria and used * to transform the Village Records into the desired OM or business objects. */ private Class returnBuilderClass = null; /** * A reference to the method in the return builder class that will * convert the Village Records to the desired class. */ private Method populateObjectsMethod = null; /** * The default value (">") used to indicate that the total number of * records or pages is unknown. You can use <code>setMoreIndicator()</code> * to change this to whatever value you like (e.g. "more than"). */ public static final String DEFAULT_MORE_INDICATOR = ">"; private static String moreIndicator = DEFAULT_MORE_INDICATOR; /** * The default value for the maximum number of pages of data to be retained * in memory - you can provide your own default value using * <code>setMemoryPageLimit()</code>. */ public static final int DEFAULT_MEMORY_LIMIT_PAGES = 5; private static int memoryPageLimit = DEFAULT_MEMORY_LIMIT_PAGES; /** A place to store search parameters that relate to this query. */ private Hashtable params = null; /** Logging */ private static Log log = LogFactory.getLog(LargeSelect.class); /** * Creates a LargeSelect whose results are returned as a <code>List</code> * containing a maximum of <code>pageSize</code> Village <code>Record</code> * objects at a time, maintaining a maximum of * <code>LargeSelect.memoryPageLimit</code> pages of results in memory. * * @param criteria object used by BasePeer to build the query. In order to * allow this class to utilise database server implemented offsets and * limits (when available), the provided criteria must not have any limit or * offset defined. * @param pageSize number of rows to return in one block. * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit, or if <code>pageSize</code> is less than 1; */ public LargeSelect(Criteria criteria, int pageSize) throws IllegalArgumentException { this(criteria, pageSize, LargeSelect.memoryPageLimit); } /** * Creates a LargeSelect whose results are returned as a <code>List</code> * containing a maximum of <code>pageSize</code> Village <code>Record</code> * objects at a time, maintaining a maximum of <code>memoryPageLimit</code> * pages of results in memory. * * @param criteria object used by BasePeer to build the query. In order to * allow this class to utilise database server implemented offsets and * limits (when available), the provided criteria must not have any limit or * offset defined. * @param pageSize number of rows to return in one block. * @param memoryPageLimit maximum number of pages worth of rows to be held * in memory at one time. * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit, or if <code>pageSize</code> or * <code>memoryLimitPages</code> are less than 1; */ public LargeSelect(Criteria criteria, int pageSize, int memoryPageLimit) throws IllegalArgumentException { init(criteria, pageSize, memoryPageLimit); } /** * Creates a LargeSelect whose results are returned as a <code>List</code> * containing a maximum of <code>pageSize</code> objects of the type * defined within the class named <code>returnBuilderClassName</code> at a * time, maintaining a maximum of <code>LargeSelect.memoryPageLimit</code> * pages of results in memory. * * @param criteria object used by BasePeer to build the query. In order to * allow this class to utilise database server implemented offsets and * limits (when available), the provided criteria must not have any limit or * offset defined. If the criteria does not include the definition of any * select columns the <code>addSelectColumns(Criteria)</code> method of * the class named as <code>returnBuilderClassName</code> will be used to * add them. * @param pageSize number of rows to return in one block. * @param returnBuilderClassName The name of the class that will be used to * build the result records (may implement <code>addSelectColumns(Criteria) * </code> and must implement <code>populateObjects(List)</code>). * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit, if <code>pageSize</code> is less than 1, or if * problems are experienced locating and invoking either one or both of * <code>addSelectColumns(Criteria)</code> and <code> populateObjects(List) * </code> in the class named <code>returnBuilderClassName</code>. */ public LargeSelect( Criteria criteria, int pageSize, String returnBuilderClassName) throws IllegalArgumentException { this( criteria, pageSize, LargeSelect.memoryPageLimit, returnBuilderClassName); } /** * Creates a LargeSelect whose results are returned as a <code>List</code> * containing a maximum of <code>pageSize</code> objects of the type * defined within the class named <code>returnBuilderClassName</code> at a * time, maintaining a maximum of <code>memoryPageLimit</code> pages of * results in memory. * * @param criteria object used by BasePeer to build the query. In order to * allow this class to utilise database server implemented offsets and * limits (when available), the provided criteria must not have any limit or * offset defined. If the criteria does not include the definition of any * select columns the <code>addSelectColumns(Criteria)</code> method of * the class named as <code>returnBuilderClassName</code> will be used to * add them. * @param pageSize number of rows to return in one block. * @param memoryPageLimit maximum number of pages worth of rows to be held * in memory at one time. * @param returnBuilderClassName The name of the class that will be used to * build the result records (may implement <code>addSelectColumns(Criteria) * </code> and must implement <code>populateObjects(List)</code>). * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit, if <code>pageSize</code> or <code> * memoryLimitPages</code> are less than 1, or if problems are experienced * locating and invoking either one or both of <code> * addSelectColumns(Criteria)</code> and <code> populateObjects(List)</code> * in the class named <code>returnBuilderClassName</code>. */ public LargeSelect( Criteria criteria, int pageSize, int memoryPageLimit, String returnBuilderClassName) throws IllegalArgumentException { try { this.returnBuilderClass = Class.forName(returnBuilderClassName); // Add the select columns if necessary. if (criteria.getSelectColumns().size() == 0) { Class[] argTypes = { Criteria.class }; Method selectColumnAdder = returnBuilderClass.getMethod("addSelectColumns", argTypes); Object[] theArgs = { criteria }; selectColumnAdder.invoke(returnBuilderClass.newInstance(), theArgs); } // Locate the populateObjects() method - this will be used later Class[] argTypes = { List.class }; populateObjectsMethod = returnBuilderClass.getMethod("populateObjects", argTypes); } catch (Exception e) { throw new IllegalArgumentException( "The class named as returnBuilderClassName does not " + "provide the necessary facilities - see javadoc."); } init(criteria, pageSize, memoryPageLimit); } /** * Called by the constructors to start the query. * * @param criteria Object used by <code>BasePeer</code> to build the query. * In order to allow this class to utilise database server implemented * offsets and limits (when available), the provided criteria must not have * any limit or offset defined. * @param pageSize number of rows to return in one block. * @param memoryLimitPages maximum number of pages worth of rows to be held * in memory at one time. * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit and if <code>pageSize</code> or * <code>memoryLimitPages</code> are less than 1; */ private void init(Criteria criteria, int pageSize, int memoryLimitPages) throws IllegalArgumentException { if (criteria.getOffset() != 0 || criteria.getLimit() != -1) { throw new IllegalArgumentException( "criteria must not use Offset and/or Limit."); } if (pageSize < 1) { throw new IllegalArgumentException( "pageSize must be greater than zero."); } if (memoryLimitPages < 1) { throw new IllegalArgumentException( "memoryPageLimit must be greater than zero."); } this.pageSize = pageSize; this.memoryLimit = pageSize * memoryLimitPages; this.criteria = criteria; dbName = criteria.getDbName(); blockEnd = blockBegin + memoryLimit - 1; startQuery(pageSize); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -