Open Source Repository

Home /hibernate/hibernate-3.2.7.ga | Repository Home


org/hibernate/jdbc/AbstractBatcher.java
//$Id: AbstractBatcher.java 14999 2008-07-31 15:10:43Z [email protected] $
package org.hibernate.jdbc;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ConcurrentModificationException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.ScrollMode;
import org.hibernate.TransactionException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.pretty.Formatter;
import org.hibernate.util.GetGeneratedKeysHelper;
import org.hibernate.util.JDBCExceptionReporter;
import org.hibernate.util.NamedGeneratedKeysHelper;

/**
 * Manages prepared statements and batching.
 *
 @author Gavin King
 */
public abstract class AbstractBatcher implements Batcher {

  private static int globalOpenPreparedStatementCount;
  private static int globalOpenResultSetCount;

  private int openPreparedStatementCount;
  private int openResultSetCount;

  protected static final Log log = LogFactory.getLog(AbstractBatcher.class);
  protected static final Log SQL_LOG = LogFactory.getLog("org.hibernate.SQL");

  private final ConnectionManager connectionManager;
  private final SessionFactoryImplementor factory;

  private PreparedStatement batchUpdate;
  private String batchUpdateSQL;

  private HashSet statementsToClose = new HashSet();
  private HashSet resultSetsToClose = new HashSet();
  private PreparedStatement lastQuery;

  private boolean releasing = false;
  private final Interceptor interceptor;

  private long transactionTimeout = -1;
  boolean isTransactionTimeoutSet;

  public AbstractBatcher(ConnectionManager connectionManager, Interceptor interceptor) {
    this.connectionManager = connectionManager;
    this.interceptor = interceptor;
    this.factory = connectionManager.getFactory();
  }

  public void setTransactionTimeout(int seconds) {
    isTransactionTimeoutSet = true;
    transactionTimeout = System.currentTimeMillis() 1000 + seconds;
  }

  public void unsetTransactionTimeout() {
    isTransactionTimeoutSet = false;
  }

  protected PreparedStatement getStatement() {
    return batchUpdate;
  }

  public CallableStatement prepareCallableStatement(String sql)
  throws SQLException, HibernateException {
    executeBatch();
    logOpenPreparedStatement();
    return getCallableStatementconnectionManager.getConnection(), sql, false);
  }

  public PreparedStatement prepareStatement(String sql)
  throws SQLException, HibernateException {
    return prepareStatementsql, false );
  }

  public PreparedStatement prepareStatement(String sql, boolean getGeneratedKeys)
      throws SQLException, HibernateException {
    executeBatch();
    logOpenPreparedStatement();
    return getPreparedStatement(
        connectionManager.getConnection(),
            sql,
            false,
            getGeneratedKeys,
            null,
            null,
            false
    );
  }

  public PreparedStatement prepareStatement(String sql, String[] columnNames)
      throws SQLException, HibernateException {
    executeBatch();
    logOpenPreparedStatement();
    return getPreparedStatement(
        connectionManager.getConnection(),
            sql,
            false,
            false,
            columnNames,
            null,
            false
    );
  }

  public PreparedStatement prepareSelectStatement(String sql)
      throws SQLException, HibernateException {
    logOpenPreparedStatement();
    return getPreparedStatement(
        connectionManager.getConnection(),
            sql,
            false,
            false,
            null,
            null,
            false
    );
  }

  public PreparedStatement prepareQueryStatement(
      String sql,
          boolean scrollable,
          ScrollMode scrollModethrows SQLException, HibernateException {
    logOpenPreparedStatement();
    PreparedStatement ps = getPreparedStatement(
        connectionManager.getConnection(),
            sql,
            scrollable,
            scrollMode
    );
    setStatementFetchSizeps );
    statementsToClose.addps );
    lastQuery = ps;
    return ps;
  }

  public CallableStatement prepareCallableQueryStatement(
      String sql,
          boolean scrollable,
          ScrollMode scrollModethrows SQLException, HibernateException {
    logOpenPreparedStatement();
    CallableStatement ps = CallableStatement getPreparedStatement(
        connectionManager.getConnection(),
            sql,
            scrollable,
            false,
            null,
            scrollMode,
            true
    );
    setStatementFetchSizeps );
    statementsToClose.addps );
    lastQuery = ps;
    return ps;
  }

  public void abortBatch(SQLException sqle) {
    try {
      if (batchUpdate!=nullcloseStatement(batchUpdate);
    }
    catch (SQLException e) {
      //noncritical, swallow and let the other propagate!
      JDBCExceptionReporter.logExceptions(e);
    }
    finally {
      batchUpdate=null;
      batchUpdateSQL=null;
    }
  }

  public ResultSet getResultSet(PreparedStatement psthrows SQLException {
    ResultSet rs = ps.executeQuery();
    resultSetsToClose.add(rs);
    logOpenResults();
    return rs;
  }

  public ResultSet getResultSet(CallableStatement ps, Dialect dialectthrows SQLException {
    ResultSet rs = dialect.getResultSet(ps);
    resultSetsToClose.add(rs);
    logOpenResults();
    return rs;

  }

  public void closeQueryStatement(PreparedStatement ps, ResultSet rsthrows SQLException {
    boolean psStillThere = statementsToClose.removeps );
    try {
      if rs != null ) {
        if resultSetsToClose.removers ) ) {
          logCloseResults();
          rs.close();
        }
      }
    }
    finally {
      if psStillThere ) {
        closeQueryStatementps );
      }
    }
  }

  public PreparedStatement prepareBatchStatement(String sql)
      throws SQLException, HibernateException {
    sql = getSQLsql );

    if !sql.equals(batchUpdateSQL) ) {
      batchUpdate=prepareStatement(sql)// calls executeBatch()
      batchUpdateSQL=sql;
    }
    else {
      log.debug("reusing prepared statement");
      log(sql);
    }
    return batchUpdate;
  }

  public CallableStatement prepareBatchCallableStatement(String sql)
      throws SQLException, HibernateException {
    if !sql.equals(batchUpdateSQL) ) { // TODO: what if batchUpdate is a callablestatement ?
      batchUpdate=prepareCallableStatement(sql)// calls executeBatch()
      batchUpdateSQL=sql;
    }
    return (CallableStatement)batchUpdate;
  }


  public void executeBatch() throws HibernateException {
    if (batchUpdate!=null) {
      try {
        try {
          doExecuteBatch(batchUpdate);
        }
        finally {
          closeStatement(batchUpdate);
        }
      }
      catch (SQLException sqle) {
        throw JDBCExceptionHelper.convert(
                factory.getSQLExceptionConverter(),
                sqle,
                "Could not execute JDBC batch update",
                batchUpdateSQL
          );
      }
      finally {
        batchUpdate=null;
        batchUpdateSQL=null;
      }
    }
  }

  public void closeStatement(PreparedStatement psthrows SQLException {
    logClosePreparedStatement();
    closePreparedStatement(ps);
  }

  private void closeQueryStatement(PreparedStatement psthrows SQLException {

    try {
      //work around a bug in all known connection pools....
      if ps.getMaxRows()!=ps.setMaxRows(0);
      if ps.getQueryTimeout()!=ps.setQueryTimeout(0);
    }
    catch (Exception e) {
      log.warn("exception clearing maxRows/queryTimeout", e);
//      ps.close(); //just close it; do NOT try to return it to the pool!
      return//NOTE: early exit!
    }
    finally {
      closeStatement(ps);
    }

    if lastQuery==ps lastQuery = null;

  }

  /**
   * Actually releases the batcher, allowing it to cleanup internally held
   * resources.
   */
  public void closeStatements() {
    try {
      releasing = true;

      try {
        if batchUpdate != null ) {
          batchUpdate.close();
        }
      }
      catch SQLException sqle ) {
        //no big deal
        log.warn"Could not close a JDBC prepared statement", sqle );
      }
      batchUpdate = null;
      batchUpdateSQL = null;

      Iterator iter = resultSetsToClose.iterator();
      while iter.hasNext() ) {
        try {
          logCloseResults();
          ( ( ResultSet iter.next() ).close();
        }
        catch SQLException e ) {
          // no big deal
          log.warn"Could not close a JDBC result set", e );
        }
        catch ConcurrentModificationException e ) {
          // this has been shown to happen occasionally in rare cases
          // when using a transaction manager + transaction-timeout
          // where the timeout calls back through Hibernate's
          // registered transaction synchronization on a separate
          // "reaping" thread.  In cases where that reaping thread
          // executes through this block at the same time the main
          // application thread does we can get into situations where
          // these CMEs occur.  And though it is not "allowed" per-se,
          // the end result without handling it specifically is infinite
          // looping.  So here, we simply break the loop
          log.info"encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" );
          break;
        }
        catch Throwable e ) {
          // sybase driver (jConnect) throwing NPE here in certain
          // cases, but we'll just handle the general "unexpected" case
          log.warn"Could not close a JDBC result set", e );
        }
      }
      resultSetsToClose.clear();

      iter = statementsToClose.iterator();
      while iter.hasNext() ) {
        try {
          closeQueryStatement( ( PreparedStatement iter.next() );
        }
        catch ConcurrentModificationException e ) {
          // see explanation above...
          log.info"encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" );
          break;
        }
        catch SQLException e ) {
          // no big deal
          log.warn"Could not close a JDBC statement", e );
        }
      }
      statementsToClose.clear();
    }
    finally {
      releasing = false;
    }
  }

  protected abstract void doExecuteBatch(PreparedStatement psthrows SQLException, HibernateException;

  private String preparedStatementCountsToString() {
    return
        " (open PreparedStatements: " +
        openPreparedStatementCount +
        ", globally: " +
        globalOpenPreparedStatementCount +
        ")";
  }

  private String resultSetCountsToString() {
    return
        " (open ResultSets: " +
        openResultSetCount +
        ", globally: " +
        globalOpenResultSetCount +
        ")";
  }

  private void logOpenPreparedStatement() {
    if log.isDebugEnabled() ) {
      log.debug"about to open PreparedStatement" + preparedStatementCountsToString() );
      openPreparedStatementCount++;
      globalOpenPreparedStatementCount++;
    }
  }

  private void logClosePreparedStatement() {
    if log.isDebugEnabled() ) {
      log.debug"about to close PreparedStatement" + preparedStatementCountsToString() );
      openPreparedStatementCount--;
      globalOpenPreparedStatementCount--;
    }
  }

  private void logOpenResults() {
    if log.isDebugEnabled() ) {
      log.debug"about to open ResultSet" + resultSetCountsToString() );
      openResultSetCount++;
      globalOpenResultSetCount++;
    }
  }
  private void logCloseResults() {
    if log.isDebugEnabled() ) {
      log.debug"about to close ResultSet" + resultSetCountsToString() );
      openResultSetCount--;
      globalOpenResultSetCount--;
    }
  }

  protected SessionFactoryImplementor getFactory() {
    return factory;
  }

  private void log(String sql) {
    if SQL_LOG.isDebugEnabled() ) {
      SQL_LOG.debugformat(sql) );
    }
    if factory.getSettings().isShowSqlEnabled() ) {
      System.out.println"Hibernate: " + format(sql) );
    }
  }

  private String format(String sql) {
    if factory.getSettings().isFormatSqlEnabled() ) {
      return new Formatter(sql).format();
    }
    else {
      return sql;
    }
  }

  private PreparedStatement getPreparedStatement(
      final Connection conn,
          final String sql,
          final boolean scrollable,
          final ScrollMode scrollMode)
  throws SQLException {
    return getPreparedStatement(
        conn,
            sql,
            scrollable,
            false,
            null,
            scrollMode,
            false
    );
  }

  private CallableStatement getCallableStatement(
      final Connection conn,
          String sql,
          boolean scrollablethrows SQLException {
    if scrollable && !factory.getSettings().isScrollableResultSetsEnabled() ) {
      throw new AssertionFailure("scrollable result sets are not enabled");
    }

    sql = getSQLsql );
    logsql );

    log.trace("preparing callable statement");
    if scrollable ) {
      return conn.prepareCall(
          sql,
              ResultSet.TYPE_SCROLL_INSENSITIVE,
              ResultSet.CONCUR_READ_ONLY
      );
    }
    else {
      return conn.prepareCallsql );
    }
  }

  private String getSQL(String sql) {
    sql = interceptor.onPrepareStatementsql );
    if sql==null || sql.length() == ) {
      throw new AssertionFailure"Interceptor.onPrepareStatement() returned null or empty string." );
    }
    return sql;
  }

  private PreparedStatement getPreparedStatement(
      final Connection conn,
          String sql,
          boolean scrollable,
          final boolean useGetGeneratedKeys,
          final String[] namedGeneratedKeys,
          final ScrollMode scrollMode,
          final boolean callablethrows SQLException {
    if scrollable && !factory.getSettings().isScrollableResultSetsEnabled() ) {
      throw new AssertionFailure("scrollable result sets are not enabled");
    }
    if useGetGeneratedKeys && !factory.getSettings().isGetGeneratedKeysEnabled() ) {
      throw new AssertionFailure("getGeneratedKeys() support is not enabled");
    }

    sql = getSQLsql );
    logsql );

    log.trace"preparing statement" );
    PreparedStatement result;
    if scrollable ) {
      if callable ) {
        result = conn.prepareCallsql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY );
      }
      else {
        result = conn.prepareStatementsql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY );
      }
    }
    else if useGetGeneratedKeys ) {
      result = GetGeneratedKeysHelper.prepareStatementconn, sql );
    }
    else if namedGeneratedKeys != null ) {
      result = NamedGeneratedKeysHelper.prepareStatementconn, sql, namedGeneratedKeys );
    }
    else {
      if callable ) {
        result = conn.prepareCallsql );
      }
      else {
        result = conn.prepareStatementsql );
      }
    }

    setTimeoutresult );

    if factory.getStatistics().isStatisticsEnabled() ) {
      factory.getStatisticsImplementor().prepareStatement();
    }

    return result;

  }

  private void setTimeout(PreparedStatement resultthrows SQLException {
    if isTransactionTimeoutSet ) {
      int timeout = (int) ( transactionTimeout - System.currentTimeMillis() 1000 ) );
      if (timeout<=0) {
        throw new TransactionException("transaction timeout expired");
      }
      else {
        result.setQueryTimeout(timeout);
      }
    }
  }

  private void closePreparedStatement(PreparedStatement psthrows SQLException {
    try {
      log.trace("closing statement");
      ps.close();
      if factory.getStatistics().isStatisticsEnabled() ) {
        factory.getStatisticsImplementor().closeStatement();
      }
    }
    finally {
      if !releasing ) {
        // If we are in the process of releasing, no sense
        // checking for aggressive-release possibility.
        connectionManager.afterStatement();
      }
    }
  }

  private void setStatementFetchSize(PreparedStatement statementthrows SQLException {
    Integer statementFetchSize = factory.getSettings().getJdbcFetchSize();
    if statementFetchSize!=null ) {
      statement.setFetchSizestatementFetchSize.intValue() );
    }
  }

  public Connection openConnection() throws HibernateException {
    log.debug("opening JDBC connection");
    try {
      return factory.getConnectionProvider().getConnection();
    }
    catch (SQLException sqle) {
      throw JDBCExceptionHelper.convert(
          factory.getSQLExceptionConverter(),
              sqle,
              "Cannot open connection"
        );
    }
  }

  public void closeConnection(Connection connthrows HibernateException {
    if conn == null ) {
      log.debug"found null connection on AbstractBatcher#closeConnection" );
      // EARLY EXIT!!!!
      return;
    }

    if log.isDebugEnabled() ) {
      log.debug"closing JDBC connection" + preparedStatementCountsToString() + resultSetCountsToString() );
    }

    try {
      if !conn.isClosed() ) {
        JDBCExceptionReporter.logAndClearWarningsconn );
      }
      factory.getConnectionProvider().closeConnectionconn );
    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convertfactory.getSQLExceptionConverter(), sqle, "Cannot close connection" );
    }
  }

  public void cancelLastQuery() throws HibernateException {
    try {
      if (lastQuery!=nulllastQuery.cancel();
    }
    catch (SQLException sqle) {
      throw JDBCExceptionHelper.convert(
          factory.getSQLExceptionConverter(),
              sqle,
              "Cannot cancel query"
        );
    }
  }

  public boolean hasOpenResources() {
    return resultSetsToClose.size() || statementsToClose.size() 0;
  }

  public String openResourceStatsAsString() {
    return preparedStatementCountsToString() + resultSetCountsToString();
  }

}