![JAR search and dependency download from the Maven repository](/logo.png)
panda.dao.sql.executor.JdbcSqlResultSet Maven / Gradle / Ivy
Show all versions of panda-core Show documentation
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);
}
}
}