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

com.databasesandlife.util.jdbc.ResultSetIterator Maven / Gradle / Ivy

There is a newer version: 21.0.1
Show newest version
package com.databasesandlife.util.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Takes a {@link PreparedStatement} and creates a {@link ResultSet} and
 * implements the {@link Iterator} interface that returns the results.
 * 

* Subclasses may be created which convert a particular row into a particular type of object * to be returned from the iterator. *

* It is intended for those ResultSets which can only iterate forward (i.e. streaming results). * On these ResultSets the aResultSet.hasNext * method cannot be called. * Therefore the ResultSet only has a next method, * but the Iterator must provide a hasNext * method which can be called an arbitrary number of times to see if there would be a next row. * The algorithm employed is to always fetch one row ahead store it in the object, so hasNext * can return if there is a next row or not. The next method then returns that previously fetched one, and * fetches the next one. *

* Any {@link SQLException} which are thrown from the ResultSet are assumed to be "should never happen" * type, and thus are transformed into {@link RuntimeException}s. *

* The ResultSet is opened on the first next or hasNext method, and closed at the end of the iteration. * This means that an object of this type can be created then used hours later; the ResultSet will not have timed out, as the * statement is executed only briefly before the results are needed. *

* The PreparedStatement, and the associated Connection, can also be closed at the end of the iteration if desired * (if this is the only code which will be using that statement) or can be left open (if other code will use it). *

* Usage: *

 * public class MyDocResultSetIterator extends ResultSetIterator<MyDoc> {
 *     protected MyDoc objectForRow(ResultSet r) throws SQLException {
 *         MyDoc result = new MyDoc();
 *         result.setVal(r.getString("val"));
 *         return result;
 *     }
 * }
 * public Iterator<MyDoc> foo() {
 *     PreparedStatement stat = aConnection.prepareStatement(
 *         "SELECT val FROM big_table",
 *         ResultSet.TYPE_FORWARD_ONLY,
 *         ResultSet.CONCUR_READ_ONLY);
 *     stat.setFetchSize(Integer.MIN_VALUE);
 *     boolean shouldClosePreparedStatement = truel
 *     return new MyDocResultSetIterator("...", stat, shouldClosePreparedStatement);
 * }
 * 
*

* See Reading row-by-row into Java from MySQL. *

* This code has beem tested using Java 6 with MySQL 5.0 and the JDBC driver "MySQL Connector" 5.1.15. * * @author This source is copyright Adrian Smith and licensed under the LGPL 3. * @see Project on GitHub */ public abstract class ResultSetIterator implements Iterator { protected enum State { BEFORE_EXECUTE, /** nextObject != null */ RETURN_RESULTS, /** resultSet closed */ FINISHED }; public enum CloseStrategy { CLOSE_NOTHING, CLOSE_STATEMENT, /** Closes statement and connection */ CLOSE_CONNECTION } protected State state = State.BEFORE_EXECUTE; protected final String sqlForLog; protected final PreparedStatement statement; protected final CloseStrategy closeStrategy; protected ResultSet resultSet; protected T nextObject; /** * @param sqlForLog What SQL was executed? This is only used to populate the text of Exceptions */ public ResultSetIterator(String sqlForLog, PreparedStatement statement, CloseStrategy closeStrategy) { this.sqlForLog = sqlForLog; this.statement = statement; this.closeStrategy = closeStrategy; } protected synchronized void stateTransitionToReturnResults() { try { if (state != State.BEFORE_EXECUTE) return; resultSet = statement.executeQuery(); state = State.RETURN_RESULTS; afterQueryExecuted(); next(); // load "nextObject" attribute; can stateTransitionToFinished } catch (SQLException e) { throw new RuntimeException(sqlForLog + ": " + e.getMessage(), e); } } /** * Can be overridden to do things just after the ResultSet has been executed but before any rows have been read. * For example process metadata. */ protected void afterQueryExecuted() throws SQLException { } protected synchronized void stateTransitionToFinished() { try { if (state != State.RETURN_RESULTS) return; resultSet.close(); var connection = statement.getConnection(); switch (closeStrategy) { case CLOSE_NOTHING: break; case CLOSE_STATEMENT: statement.close(); break; case CLOSE_CONNECTION: statement.close(); connection.close(); break; } state = State.FINISHED; } catch (SQLException e) { throw new RuntimeException(sqlForLog + ": " + e.getMessage(), e); } } /** * Looks in the current data in the resultSet and * returns a new object to be returned from the Iterator. * @param r cursor is already at the row containing the data. * @return new object representing the data in the ResultSet. * @throws SQLException if there is some problem reading the ResultSet. * (This is converted into a RuntimeException by {@link #next}, so there is * no point implementing the same code in the subclass' implementation of this method.) */ protected abstract T newObjectForRow(ResultSet r) throws SQLException; public boolean hasNext() { if (state == State.BEFORE_EXECUTE) stateTransitionToReturnResults(); if (state == State.FINISHED) return false; return true; } public T next() { try { if (state == State.BEFORE_EXECUTE) stateTransitionToReturnResults(); if (state == State.FINISHED) throw new NoSuchElementException(); var result = nextObject; if (resultSet.next()) nextObject = newObjectForRow(resultSet); else stateTransitionToFinished(); return result; } catch (SQLException e) { throw new RuntimeException(sqlForLog + ": " + e.getMessage(), e); } } public void remove() { throw new UnsupportedOperationException(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy