All Downloads are FREE. Search and download functionalities are using the official Maven repository.

panda.dao.sql.executor.JdbcSqlResultSet Maven / Gradle / Ivy

Go to download

Panda Core is the core module of Panda Framework, it contains commonly used utility classes similar to apache-commons.

There is a newer version: 1.8.0
Show newest version
package panda.dao.sql.executor;

import java.lang.reflect.Type;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import panda.bean.BeanHandler;
import panda.dao.DaoNamings;
import panda.dao.DaoTypes;
import panda.dao.sql.SqlResultSet;
import panda.dao.sql.Sqls;
import panda.dao.sql.adapter.TypeAdapter;
import panda.lang.Classes;
import panda.lang.reflect.Types;
import panda.log.Log;
import panda.log.Logs;

/**
 * @param  the record type
 */
public class JdbcSqlResultSet implements SqlResultSet {
	protected static Log log = Logs.getLog(JdbcSqlResultSet.class);

	protected JdbcSqlExecutor executor;
	protected ResultSet resultSet;

	/**
	 * ResultColumn
	 */
	private class ResultColumn {
		protected int columnIndex;
		protected String columnLabel;
		protected int columnType;
		protected String jdbcType;
		protected String propertyName;
		protected Type propertyType;
		@SuppressWarnings("rawtypes")
		protected TypeAdapter typeAdapter;
	}

	private List resultColumns;
	private BeanHandler beanHandler;

	/**
	 * Constructor
	 * @param executor the sql executor
	 * @param resultSet the sql result set
	 * @param resultType the result bean type
	 * @throws SQLException if a SQL error occurs
	 */
	public JdbcSqlResultSet(JdbcSqlExecutor executor, ResultSet resultSet, Class resultType)
			throws SQLException {
		this(executor, resultSet, resultType, null, null);
	}
	
	/**
	 * Constructor
	 * @param executor the sql executor
	 * @param resultSet the sql result set
	 * @param resultType the result bean type
	 * @param resultObject the initial result object
	 * @throws SQLException if a SQL error occurs
	 */
	public JdbcSqlResultSet(JdbcSqlExecutor executor, ResultSet resultSet, Class resultType, T resultObject)
			throws SQLException {
		this(executor, resultSet, resultType, resultObject, null);
	}
	
	/**
	 * Constructor
	 * @param executor the sql executor
	 * @param resultSet the sql result set
	 * @param resultType the result bean type
	 * @param resultObject the initial result object
	 * @param keyProp the generated key property
	 * @throws SQLException if a SQL error occurs
	 */
	public JdbcSqlResultSet(JdbcSqlExecutor executor, ResultSet resultSet, Class resultType, T resultObject, String keyProp)
			throws SQLException {
		this.executor = executor;
		this.resultSet = resultSet;
		init(resultType, resultObject, keyProp);
	}

	private void init(Class beanType, T resultObject, String keyProp) throws SQLException {
		if (!Classes.isImmutable(beanType)) {
			beanHandler = executor.getBeans().getBeanHandler(beanType);
		}
		resultColumns = new ArrayList();

		ResultSetMetaData meta = resultSet.getMetaData();
		if (keyProp == null && beanHandler == null && meta.getColumnCount() != 1) {
			throw new IllegalArgumentException("Too many result columns for the result: " + beanType);
		}

		int cnt = meta.getColumnCount();
		for (int i = 1; i <= cnt; i++) {
			ResultColumn rc = new ResultColumn();
			rc.columnIndex = i;
			rc.columnLabel = meta.getColumnLabel(i);
			rc.columnType = meta.getColumnType(i);
			rc.jdbcType = DaoTypes.getType(rc.columnType);
			if (keyProp == null) {
				rc.propertyName = DaoNamings.columnLabel2JavaName(rc.columnLabel);
			}
			else {
				if (cnt > 1) {
					rc.propertyName = DaoNamings.columnLabel2JavaName(rc.columnLabel);
					if (!keyProp.equals(rc.propertyName)) {
						continue;
					}
				}
				else {
					rc.propertyName = keyProp;
				}
			}

			rc.propertyType = beanHandler == null ? beanType : beanHandler.getBeanType(resultObject, rc.propertyName);
			if (rc.propertyType == null) {
				if (rc.columnLabel.endsWith("_")) {
					// skip unmapping special column
					continue;
				}
				else {
					throw new IllegalArgumentException("Unknown type for property " + rc.propertyName);
				}
			}
			
			rc.typeAdapter = executor.getTypeAdapters().getTypeAdapter(Types.getRawType(rc.propertyType), rc.jdbcType);
			if (rc.typeAdapter == null) {
				throw new IllegalArgumentException("Unknown TypeAdapter for " + rc.propertyName + "["
						+ rc.propertyType + " <-> " + rc.jdbcType + "].");
			}
			resultColumns.add(rc);
		}
		
		if (resultColumns.isEmpty()) {
			throw new IllegalArgumentException("Failed to init column mapping for " + meta + " -> " + beanType);
		}
	}

	// ---------------------------------------------------------------------
	// Get/Update
	// ---------------------------------------------------------------------
	/**
	 * @return the resultSet
	 */
	public ResultSet getResultSet() {
		return resultSet;
	}

	/**
	 * returns data to populate a single object instance.
	 * 
	 * @return The single result object populated with the result set data, or null if no result was
	 *         found
	 * @throws SQLException If anSQL error occurs.
	 */
	public T getResult() throws SQLException {
		return getResult((T)null);
	}

	/**
	 * returns data to populate a single object instance.
	 * 
	 * @param resultObject The result object instance that should be populated with result data.
	 * @return The single result object as supplied by the resultObject parameter, populated with
	 *         the result set data, or null if no result was found
	 * @throws SQLException If a SQL error occurs.
	 */
	@SuppressWarnings("unchecked")
	public T getResult(T resultObject) throws SQLException {
		if (beanHandler == null) {
			ResultColumn rc = resultColumns.get(0);
			resultObject = (T)rc.typeAdapter.getResult(resultSet, rc.columnIndex);
		}
		else {
			if (resultObject == null) {
				resultObject = beanHandler.createObject();
			}

			for (ResultColumn rc : resultColumns) {
				Object value = rc.typeAdapter.getResult(resultSet, rc.columnIndex);
				if (!beanHandler.setBeanValue(resultObject, rc.propertyName, value)) {
					log.warn("Failed to set " + rc.propertyName + " of " + resultObject.getClass());
				}
			}
		}
		return resultObject;
	}

	/**
	 * update data to result set.
	 * 
	 * @param resultObject The result data object.
	 * @throws SQLException If anSQL error occurs.
	 */
	@SuppressWarnings("unchecked")
	public void updateResult(T resultObject) throws SQLException {
		for (ResultColumn rc : resultColumns) {
			Object value = beanHandler.getBeanValue(resultObject, rc.propertyName);
			rc.typeAdapter.updateResult(resultSet, rc.columnIndex, value);
		}
	}

	// ---------------------------------------------------------------------
	// Traversal/Positioning
	// ---------------------------------------------------------------------
	/**
	 * Retrieves whether the cursor is before the first row in this ResultSet object.
	 * 
	 * @return true if the cursor is before the first row; false if the
	 *         cursor is at any other position or the result set contains no rows
	 * @exception SQLException if a database access error occurs
	 */
	public boolean isBeforeFirst() throws SQLException {
		return resultSet.isBeforeFirst();
	}

	/**
	 * Retrieves whether the cursor is after the last row in this ResultSet object.
	 * 
	 * @return true if the cursor is after the last row; false if the
	 *         cursor is at any other position or the result set contains no rows
	 * @exception SQLException if a database access error occurs
	 */
	public boolean isAfterLast() throws SQLException {
		return resultSet.isAfterLast();
	}

	/**
	 * Retrieves whether the cursor is on the first row of this ResultSet object.
	 * 
	 * @return true if the cursor is on the first row; false otherwise
	 * @exception SQLException if a database access error occurs
	 */
	public boolean isFirst() throws SQLException {
		return resultSet.isFirst();
	}

	/**
	 * Retrieves whether the cursor is on the last row of this ResultSet object. Note:
	 * Calling the method isLast may be expensive because the JDBC driver might need to
	 * fetch ahead one row in order to determine whether the current row is the last row in the
	 * result set.
	 * 
	 * @return true if the cursor is on the last row; false otherwise
	 * @exception SQLException if a database access error occurs
	 */
	public boolean isLast() throws SQLException {
		return resultSet.isLast();
	}

	/**
	 * Moves the cursor to the front of this ResultSet object, just before the first
	 * row. This method has no effect if the result set contains no rows.
	 * 
	 * @exception SQLException if a database access error occurs or the result set type is
	 *                TYPE_FORWARD_ONLY
	 */
	public void beforeFirst() throws SQLException {
		resultSet.beforeFirst();
	}

	/**
	 * Moves the cursor to the end of this ResultSet object, just after the last row.
	 * This method has no effect if the result set contains no rows.
	 * 
	 * @exception SQLException if a database access error occurs or the result set type is
	 *                TYPE_FORWARD_ONLY
	 */
	public void afterLast() throws SQLException {
		resultSet.afterLast();
	}

	/**
	 * Moves the cursor to the first row in this ResultSet object.
	 * 
	 * @return true if the cursor is on a valid row; false if there are no
	 *         rows in the result set
	 * @exception SQLException if a database access error occurs or the result set type is
	 *                TYPE_FORWARD_ONLY
	 */
	public boolean first() throws SQLException {
		return resultSet.first();
	}

	/**
	 * Moves the cursor to the last row in this ResultSet object.
	 * 
	 * @return true if the cursor is on a valid row; false if there are no
	 *         rows in the result set
	 * @exception SQLException if a database access error occurs or the result set type is
	 *                TYPE_FORWARD_ONLY
	 */
	public boolean last() throws SQLException {
		return resultSet.last();
	}

	/**
	 * Retrieves the current row number. The first row is number 1, the second number 2, and so on.
	 * 
	 * @return the current row number; 0 if there is no current row
	 * @exception SQLException if a database access error occurs
	 */
	public int getRow() throws SQLException {
		return resultSet.getRow();
	}

	/**
	 * Moves the cursor to the given row number in this ResultSet object.
	 * 
	 * 

* If the row number is positive, the cursor moves to the given row number with respect to the * beginning of the result set. The first row is row 1, the second is row 2, and so on. * *

* If the given row number is negative, the cursor moves to an absolute row position with * respect to the end of the result set. For example, calling the method * absolute(-1) positions the cursor on the last row; calling the method * absolute(-2) moves the cursor to the next-to-last row, and so on. * *

* An attempt to position the cursor beyond the first/last row in the result set leaves the * cursor before the first row or after the last row. * *

* Note: Calling absolute(1) is the same as calling first(). * Calling absolute(-1) is the same as calling last(). * * @param row the number of the row to which the cursor should move. A positive number indicates * the row number counting from the beginning of the result set; a negative number * indicates the row number counting from the end of the result set * @return true if the cursor is on the result set; false otherwise * @exception SQLException if a database access error occurs, or the result set type is * TYPE_FORWARD_ONLY */ public boolean absolute(int row) throws SQLException { return resultSet.absolute(row); } /** * Moves the cursor a relative number of rows, either positive or negative. Attempting to move * beyond the first/last row in the result set positions the cursor before/after the the * first/last row. Calling relative(0) is valid, but does not change the cursor * position. * *

* Note: Calling the method relative(1) is identical to calling the method * next() and calling the method relative(-1) is identical to calling * the method previous(). * * @param rows an int specifying the number of rows to move from the current row; a * positive number moves the cursor forward; a negative number moves the cursor * backward * @return true if the cursor is on a row; false otherwise * @exception SQLException if a database access error occurs, there is no current row, or the * result set type is TYPE_FORWARD_ONLY */ public boolean relative(int rows) throws SQLException { return resultSet.relative(rows); } /** * Moves the cursor to the previous row in this ResultSet object. * * @return true if the cursor is on a valid row; false if it is off * the result set * @exception SQLException if a database access error occurs or the result set type is * TYPE_FORWARD_ONLY */ public boolean previous() throws SQLException { return resultSet.previous(); } /** * Moves the cursor down one row from its current position. A ResultSet cursor is * initially positioned before the first row; the first call to the method next * makes the first row the current row; the second call makes the second row the current row, * and so on. * *

* If an input stream is open for the current row, a call to the method next will * implicitly close it. A ResultSet object's warning chain is cleared when a new * row is read. * * @return true if the new current row is valid; false if there are no * more rows * @exception SQLException if a database access error occurs */ public boolean next() throws SQLException { return resultSet.next(); } // --------------------------------------------------------------------- // Row Updates // --------------------------------------------------------------------- /** * Retrieves whether the current row has been updated. The value returned depends on whether or * not the result set can detect updates. * * @return true if both (1) the row has been visibly updated by the owner or * another and (2) updates are detected * @exception SQLException if a database access error occurs * @see DatabaseMetaData#updatesAreDetected */ public boolean rowUpdated() throws SQLException { return resultSet.rowUpdated(); } /** * Retrieves whether the current row has had an insertion. The value returned depends on whether * or not this ResultSet object can detect visible inserts. * * @return true if a row has had an insertion and insertions are detected; * false otherwise * @exception SQLException if a database access error occurs * * @see DatabaseMetaData#insertsAreDetected */ public boolean rowInserted() throws SQLException { return resultSet.rowInserted(); } /** * Retrieves whether a row has been deleted. A deleted row may leave a visible "hole" in a * result set. This method can be used to detect holes in a result set. The value returned * depends on whether or not this ResultSet object can detect deletions. * * @return true if a row was deleted and deletions are detected; false * otherwise * @exception SQLException if a database access error occurs * * @see DatabaseMetaData#deletesAreDetected */ public boolean rowDeleted() throws SQLException { return resultSet.rowDeleted(); } /** * Inserts the contents of the insert row into this ResultSet object and into the * database. The cursor must be on the insert row when this method is called. * * @exception SQLException if a database access error occurs, if this method is called when the * cursor is not on the insert row, or if not all of non-nullable columns in the * insert row have been given a value */ public void insertRow() throws SQLException { resultSet.insertRow(); } /** * Updates the underlying database with the new contents of the current row of this * ResultSet object. This method cannot be called when the cursor is on the insert * row. * * @exception SQLException if a database access error occurs or if this method is called when * the cursor is on the insert row */ public void updateRow() throws SQLException { resultSet.updateRow(); } /** * Deletes the current row from this ResultSet object and from the underlying * database. This method cannot be called when the cursor is on the insert row. * * @exception SQLException if a database access error occurs or if this method is called when * the cursor is on the insert row */ public void deleteRow() throws SQLException { resultSet.deleteRow(); } /** * Refreshes the current row with its most recent value in the database. This method cannot be * called when the cursor is on the insert row. * *

* The refreshRow method provides a way for an application to explicitly tell the * JDBC driver to refetch a row(s) from the database. An application may want to call * refreshRow when caching or prefetching is being done by the JDBC driver to fetch * the latest value of a row from the database. The JDBC driver may actually refresh multiple * rows at once if the fetch size is greater than one. * *

* All values are refetched subject to the transaction isolation level and cursor sensitivity. * If refreshRow is called after calling an updater method, but before calling the * method updateRow, then the updates made to the row are lost. Calling the method * refreshRow frequently will likely slow performance. * * @exception SQLException if a database access error occurs or if this method is called when * the cursor is on the insert row */ public void refreshRow() throws SQLException { resultSet.refreshRow(); } /** * Cancels the updates made to the current row in this ResultSet object. This * method may be called after calling an updater method(s) and before calling the method * updateRow to roll back the updates made to a row. If no updates have been made * or updateRow has already been called, this method has no effect. * * @exception SQLException if a database access error occurs or if this method is called when * the cursor is on the insert row */ public void cancelRowUpdates() throws SQLException { resultSet.cancelRowUpdates(); } /** * Moves the cursor to the insert row. The current cursor position is remembered while the * cursor is positioned on the insert row. * * The insert row is a special row associated with an updatable result set. It is essentially a * buffer where a new row may be constructed by calling the updater methods prior to inserting * the row into the result set. * * Only the updater, getter, and insertRow methods may be called when the cursor is * on the insert row. All of the columns in a result set must be given a value each time this * method is called before calling insertRow. An updater method must be called * before a getter method can be called on a column value. * * @exception SQLException if a database access error occurs or the result set is not updatable */ public void moveToInsertRow() throws SQLException { resultSet.moveToInsertRow(); } /** * Moves the cursor to the remembered cursor position, usually the current row. This method has * no effect if the cursor is not on the insert row. * * @exception SQLException if a database access error occurs or the result set is not updatable */ public void moveToCurrentRow() throws SQLException { resultSet.moveToCurrentRow(); } // --------------------------------------------------------------------- // Properties // --------------------------------------------------------------------- /** * Gives a hint as to the direction in which the rows in this ResultSet object will * be processed. The initial value is determined by the Statement object that * produced this ResultSet object. The fetch direction may be changed at any time. * * @param direction an int specifying the suggested fetch direction; one of * ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or * ResultSet.FETCH_UNKNOWN * @exception SQLException if a database access error occurs or the result set type is * TYPE_FORWARD_ONLY and the fetch direction is not * FETCH_FORWARD * @see #getFetchDirection */ public void setFetchDirection(int direction) throws SQLException { resultSet.setFetchDirection(direction); } /** * Retrieves the fetch direction for this ResultSet object. * * @return the current fetch direction for this ResultSet object * @exception SQLException if a database access error occurs * @see #setFetchDirection */ public int getFetchDirection() throws SQLException { return resultSet.getFetchDirection(); } /** * Gives the JDBC driver a hint as to the number of rows that should be fetched from the * database when more rows are needed for this ResultSet object. If the fetch size * specified is zero, the JDBC driver ignores the value and is free to make its own best guess * as to what the fetch size should be. The default value is set by the Statement * object that created the result set. The fetch size may be changed at any time. * * @param rows the number of rows to fetch * @exception SQLException if a database access error occurs or the condition * 0 <= rows <= Statement.getMaxRows() is not satisfied * @see #getFetchSize */ public void setFetchSize(int rows) throws SQLException { resultSet.setFetchSize(rows); } /** * Retrieves the fetch size for this ResultSet object. * * @return the current fetch size for this ResultSet object * @exception SQLException if a database access error occurs * @see #setFetchSize */ public int getFetchSize() throws SQLException { return resultSet.getFetchSize(); } /** * Retrieves the type of this ResultSet object. The type is determined by the * Statement object that created the result set. * * @return ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @exception SQLException if a database access error occurs */ public int getType() throws SQLException { return resultSet.getType(); } /** * Retrieves the concurrency mode of this ResultSet object. The concurrency used is * determined by the Statement object that created the result set. * * @return the concurrency type, either ResultSet.CONCUR_READ_ONLY or * ResultSet.CONCUR_UPDATABLE * @exception SQLException if a database access error occurs */ public int getConcurrency() throws SQLException { return resultSet.getConcurrency(); } /** * Retrieves the first warning reported by calls on this ResultSet object. * Subsequent warnings on this ResultSet object will be chained to the * SQLWarning object that this method returns. * *

* The warning chain is automatically cleared each time a new row is read. This method may not * be called on a ResultSet object that has been closed; doing so will cause an * SQLException to be thrown. *

* Note: This warning chain only covers warnings caused by ResultSet * methods. Any warning caused by Statement methods (such as reading OUT * parameters) will be chained on the Statement object. * * @return the first SQLWarning object reported or null if there are * none * @exception SQLException if a database access error occurs or this method is called on a * closed result set */ public SQLWarning getWarnings() throws SQLException { return resultSet.getWarnings(); } /** * Clears all warnings reported on this ResultSet object. After this method is * called, the method getWarnings returns null until a new warning is * reported for this ResultSet object. * * @exception SQLException if a database access error occurs */ public void clearWarnings() throws SQLException { resultSet.clearWarnings(); } /** * Retrieves the name of the SQL cursor used by this ResultSet object. * *

* In SQL, a result table is retrieved through a cursor that is named. The current row of a * result set can be updated or deleted using a positioned update/delete statement that * references the cursor name. To insure that the cursor has the proper isolation level to * support update, the cursor's SELECT statement should be of the form * SELECT FOR UPDATE. If FOR UPDATE is omitted, the positioned updates * may fail. * *

* The JDBC API supports this SQL feature by providing the name of the SQL cursor used by a * ResultSet object. The current row of a ResultSet object is also the * current row of this SQL cursor. * *

* Note: If positioned update is not supported, a SQLException is thrown. * * @return the SQL name for this ResultSet object's cursor * @exception SQLException if a database access error occurs */ public String getCursorName() throws SQLException { return resultSet.getCursorName(); } /** * Retrieves the number, types and properties of this ResultSet object's columns. * * @return the description of this ResultSet object's columns * @exception SQLException if a database access error occurs */ public ResultSetMetaData getMetaData() throws SQLException { return resultSet.getMetaData(); } /** * Releases this ResultSet object's database and JDBC resources immediately instead * of waiting for this to happen when it is automatically closed. * *

* Note: A ResultSet object is automatically closed by the * Statement object that generated it when that Statement object is * closed, re-executed, or is used to retrieve the next result from a sequence of multiple * results. A ResultSet object is also automatically closed when it is garbage * collected. * * @exception SQLException if a database access error occurs */ public void close() throws SQLException { Statement st = resultSet.getStatement(); resultSet.close(); st.close(); } /** * Retrieves whether this ResultSet object has been closed. * A ResultSet is closed if the method close has been called on it, or if it is automatically closed. * @return true if this ResultSet object is closed; false if it is still open * @exception SQLException if a database access error occurs */ public boolean isClosed() throws SQLException { return resultSet.isClosed(); } /** * safe close the ResultSet and Statement */ public void safeClose() { Statement st = null; try { st = resultSet.getStatement(); } catch (SQLException e) { } finally { Sqls.safeClose(resultSet, st); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy