Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » jdbc » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.jdbc;
   26   
   27   import java.sql.CallableStatement;
   28   import java.sql.Connection;
   29   import java.sql.PreparedStatement;
   30   import java.sql.ResultSet;
   31   import java.sql.SQLException;
   32   import java.util.HashSet;
   33   import java.util.Iterator;
   34   import java.util.ConcurrentModificationException;
   35   
   36   import org.slf4j.Logger;
   37   import org.slf4j.LoggerFactory;
   38   
   39   import org.hibernate.AssertionFailure;
   40   import org.hibernate.HibernateException;
   41   import org.hibernate.Interceptor;
   42   import org.hibernate.ScrollMode;
   43   import org.hibernate.TransactionException;
   44   import org.hibernate.dialect.Dialect;
   45   import org.hibernate.engine.SessionFactoryImplementor;
   46   import org.hibernate.exception.JDBCExceptionHelper;
   47   import org.hibernate.jdbc.util.FormatStyle;
   48   import org.hibernate.util.JDBCExceptionReporter;
   49   
   50   /**
   51    * Manages prepared statements and batching.
   52    *
   53    * @author Gavin King
   54    */
   55   public abstract class AbstractBatcher implements Batcher {
   56   
   57   	private static int globalOpenPreparedStatementCount;
   58   	private static int globalOpenResultSetCount;
   59   
   60   	private int openPreparedStatementCount;
   61   	private int openResultSetCount;
   62   
   63   	protected static final Logger log = LoggerFactory.getLogger( AbstractBatcher.class );
   64   
   65   	private final ConnectionManager connectionManager;
   66   	private final SessionFactoryImplementor factory;
   67   
   68   	private PreparedStatement batchUpdate;
   69   	private String batchUpdateSQL;
   70   
   71   	private HashSet statementsToClose = new HashSet();
   72   	private HashSet resultSetsToClose = new HashSet();
   73   	private PreparedStatement lastQuery;
   74   
   75   	private boolean releasing = false;
   76   	private final Interceptor interceptor;
   77   
   78   	private long transactionTimeout = -1;
   79   	boolean isTransactionTimeoutSet;
   80   
   81   	public AbstractBatcher(ConnectionManager connectionManager, Interceptor interceptor) {
   82   		this.connectionManager = connectionManager;
   83   		this.interceptor = interceptor;
   84   		this.factory = connectionManager.getFactory();
   85   	}
   86   
   87   	public void setTransactionTimeout(int seconds) {
   88   		isTransactionTimeoutSet = true;
   89   		transactionTimeout = System.currentTimeMillis() / 1000 + seconds;
   90   	}
   91   
   92   	public void unsetTransactionTimeout() {
   93   		isTransactionTimeoutSet = false;
   94   	}
   95   
   96   	protected PreparedStatement getStatement() {
   97   		return batchUpdate;
   98   	}
   99   
  100   	public CallableStatement prepareCallableStatement(String sql)
  101   	throws SQLException, HibernateException {
  102   		executeBatch();
  103   		logOpenPreparedStatement();
  104   		return getCallableStatement( connectionManager.getConnection(), sql, false);
  105   	}
  106   
  107   	public PreparedStatement prepareStatement(String sql)
  108   	throws SQLException, HibernateException {
  109   		return prepareStatement( sql, false );
  110   	}
  111   
  112   	public PreparedStatement prepareStatement(String sql, boolean getGeneratedKeys)
  113   			throws SQLException, HibernateException {
  114   		executeBatch();
  115   		logOpenPreparedStatement();
  116   		return getPreparedStatement(
  117   				connectionManager.getConnection(),
  118   		        sql,
  119   		        false,
  120   		        getGeneratedKeys,
  121   		        null,
  122   		        null,
  123   		        false
  124   		);
  125   	}
  126   
  127   	public PreparedStatement prepareStatement(String sql, String[] columnNames)
  128   			throws SQLException, HibernateException {
  129   		executeBatch();
  130   		logOpenPreparedStatement();
  131   		return getPreparedStatement(
  132   				connectionManager.getConnection(),
  133   		        sql,
  134   		        false,
  135   		        false,
  136   		        columnNames,
  137   		        null,
  138   		        false
  139   		);
  140   	}
  141   
  142   	public PreparedStatement prepareSelectStatement(String sql)
  143   			throws SQLException, HibernateException {
  144   		logOpenPreparedStatement();
  145   		return getPreparedStatement(
  146   				connectionManager.getConnection(),
  147   		        sql,
  148   		        false,
  149   		        false,
  150   		        null,
  151   		        null,
  152   		        false
  153   		);
  154   	}
  155   
  156   	public PreparedStatement prepareQueryStatement(
  157   			String sql,
  158   	        boolean scrollable,
  159   	        ScrollMode scrollMode) throws SQLException, HibernateException {
  160   		logOpenPreparedStatement();
  161   		PreparedStatement ps = getPreparedStatement(
  162   				connectionManager.getConnection(),
  163   		        sql,
  164   		        scrollable,
  165   		        scrollMode
  166   		);
  167   		setStatementFetchSize( ps );
  168   		statementsToClose.add( ps );
  169   		lastQuery = ps;
  170   		return ps;
  171   	}
  172   
  173   	public CallableStatement prepareCallableQueryStatement(
  174   			String sql,
  175   	        boolean scrollable,
  176   	        ScrollMode scrollMode) throws SQLException, HibernateException {
  177   		logOpenPreparedStatement();
  178   		CallableStatement ps = ( CallableStatement ) getPreparedStatement(
  179   				connectionManager.getConnection(),
  180   		        sql,
  181   		        scrollable,
  182   		        false,
  183   		        null,
  184   		        scrollMode,
  185   		        true
  186   		);
  187   		setStatementFetchSize( ps );
  188   		statementsToClose.add( ps );
  189   		lastQuery = ps;
  190   		return ps;
  191   	}
  192   
  193   	public void abortBatch(SQLException sqle) {
  194   		try {
  195   			if (batchUpdate!=null) closeStatement(batchUpdate);
  196   		}
  197   		catch (SQLException e) {
  198   			//noncritical, swallow and let the other propagate!
  199   			JDBCExceptionReporter.logExceptions(e);
  200   		}
  201   		finally {
  202   			batchUpdate=null;
  203   			batchUpdateSQL=null;
  204   		}
  205   	}
  206   
  207   	public ResultSet getResultSet(PreparedStatement ps) throws SQLException {
  208   		ResultSet rs = ps.executeQuery();
  209   		resultSetsToClose.add(rs);
  210   		logOpenResults();
  211   		return rs;
  212   	}
  213   
  214   	public ResultSet getResultSet(CallableStatement ps, Dialect dialect) throws SQLException {
  215   		ResultSet rs = dialect.getResultSet(ps);
  216   		resultSetsToClose.add(rs);
  217   		logOpenResults();
  218   		return rs;
  219   
  220   	}
  221   
  222   	public void closeQueryStatement(PreparedStatement ps, ResultSet rs) throws SQLException {
  223   		boolean psStillThere = statementsToClose.remove( ps );
  224   		try {
  225   			if ( rs != null ) {
  226   				if ( resultSetsToClose.remove( rs ) ) {
  227   					logCloseResults();
  228   					rs.close();
  229   				}
  230   			}
  231   		}
  232   		finally {
  233   			if ( psStillThere ) {
  234   				closeQueryStatement( ps );
  235   			}
  236   		}
  237   	}
  238   
  239   	public PreparedStatement prepareBatchStatement(String sql)
  240   			throws SQLException, HibernateException {
  241   		sql = getSQL( sql );
  242   
  243   		if ( !sql.equals(batchUpdateSQL) ) {
  244   			batchUpdate=prepareStatement(sql); // calls executeBatch()
  245   			batchUpdateSQL=sql;
  246   		}
  247   		else {
  248   			log.debug("reusing prepared statement");
  249   			log(sql);
  250   		}
  251   		return batchUpdate;
  252   	}
  253   
  254   	public CallableStatement prepareBatchCallableStatement(String sql)
  255   			throws SQLException, HibernateException {
  256   		if ( !sql.equals(batchUpdateSQL) ) { // TODO: what if batchUpdate is a callablestatement ?
  257   			batchUpdate=prepareCallableStatement(sql); // calls executeBatch()
  258   			batchUpdateSQL=sql;
  259   		}
  260   		return (CallableStatement)batchUpdate;
  261   	}
  262   
  263   
  264   	public void executeBatch() throws HibernateException {
  265   		if (batchUpdate!=null) {
  266   			try {
  267   				try {
  268   					doExecuteBatch(batchUpdate);
  269   				}
  270   				finally {
  271   					closeStatement(batchUpdate);
  272   				}
  273   			}
  274   			catch (SQLException sqle) {
  275   				throw JDBCExceptionHelper.convert(
  276   				        factory.getSQLExceptionConverter(),
  277   				        sqle,
  278   				        "Could not execute JDBC batch update",
  279   				        batchUpdateSQL
  280   					);
  281   			}
  282   			finally {
  283   				batchUpdate=null;
  284   				batchUpdateSQL=null;
  285   			}
  286   		}
  287   	}
  288   
  289   	public void closeStatement(PreparedStatement ps) throws SQLException {
  290   		logClosePreparedStatement();
  291   		closePreparedStatement(ps);
  292   	}
  293   
  294   	private void closeQueryStatement(PreparedStatement ps) throws SQLException {
  295   
  296   		try {
  297   			//work around a bug in all known connection pools....
  298   			if ( ps.getMaxRows()!=0 ) ps.setMaxRows(0);
  299   			if ( ps.getQueryTimeout()!=0 ) ps.setQueryTimeout(0);
  300   		}
  301   		catch (Exception e) {
  302   			log.warn("exception clearing maxRows/queryTimeout", e);
  303   //			ps.close(); //just close it; do NOT try to return it to the pool!
  304   			return; //NOTE: early exit!
  305   		}
  306   		finally {
  307   			closeStatement(ps);
  308   		}
  309   
  310   		if ( lastQuery==ps ) lastQuery = null;
  311   
  312   	}
  313   
  314   	/**
  315   	 * Actually releases the batcher, allowing it to cleanup internally held
  316   	 * resources.
  317   	 */
  318   	public void closeStatements() {
  319   		try {
  320   			releasing = true;
  321   
  322   			try {
  323   				if ( batchUpdate != null ) {
  324   					batchUpdate.close();
  325   				}
  326   			}
  327   			catch ( SQLException sqle ) {
  328   				//no big deal
  329   				log.warn( "Could not close a JDBC prepared statement", sqle );
  330   			}
  331   			batchUpdate = null;
  332   			batchUpdateSQL = null;
  333   
  334   			Iterator iter = resultSetsToClose.iterator();
  335   			while ( iter.hasNext() ) {
  336   				try {
  337   					logCloseResults();
  338   					( ( ResultSet ) iter.next() ).close();
  339   				}
  340   				catch ( SQLException e ) {
  341   					// no big deal
  342   					log.warn( "Could not close a JDBC result set", e );
  343   				}
  344   				catch ( ConcurrentModificationException e ) {
  345   					// this has been shown to happen occasionally in rare cases
  346   					// when using a transaction manager + transaction-timeout
  347   					// where the timeout calls back through Hibernate's
  348   					// registered transaction synchronization on a separate
  349   					// "reaping" thread.  In cases where that reaping thread
  350   					// executes through this block at the same time the main
  351   					// application thread does we can get into situations where
  352   					// these CMEs occur.  And though it is not "allowed" per-se,
  353   					// the end result without handling it specifically is infinite
  354   					// looping.  So here, we simply break the loop
  355   					log.info( "encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" );
  356   					break;
  357   				}
  358   				catch ( Throwable e ) {
  359   					// sybase driver (jConnect) throwing NPE here in certain
  360   					// cases, but we'll just handle the general "unexpected" case
  361   					log.warn( "Could not close a JDBC result set", e );
  362   				}
  363   			}
  364   			resultSetsToClose.clear();
  365   
  366   			iter = statementsToClose.iterator();
  367   			while ( iter.hasNext() ) {
  368   				try {
  369   					closeQueryStatement( ( PreparedStatement ) iter.next() );
  370   				}
  371   				catch ( ConcurrentModificationException e ) {
  372   					// see explanation above...
  373   					log.info( "encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" );
  374   					break;
  375   				}
  376   				catch ( SQLException e ) {
  377   					// no big deal
  378   					log.warn( "Could not close a JDBC statement", e );
  379   				}
  380   			}
  381   			statementsToClose.clear();
  382   		}
  383   		finally {
  384   			releasing = false;
  385   		}
  386   	}
  387   
  388   	protected abstract void doExecuteBatch(PreparedStatement ps) throws SQLException, HibernateException;
  389   
  390   	private String preparedStatementCountsToString() {
  391   		return
  392   				" (open PreparedStatements: " +
  393   				openPreparedStatementCount +
  394   				", globally: " +
  395   				globalOpenPreparedStatementCount +
  396   				")";
  397   	}
  398   
  399   	private String resultSetCountsToString() {
  400   		return
  401   				" (open ResultSets: " +
  402   				openResultSetCount +
  403   				", globally: " +
  404   				globalOpenResultSetCount +
  405   				")";
  406   	}
  407   
  408   	private void logOpenPreparedStatement() {
  409   		if ( log.isDebugEnabled() ) {
  410   			log.debug( "about to open PreparedStatement" + preparedStatementCountsToString() );
  411   			openPreparedStatementCount++;
  412   			globalOpenPreparedStatementCount++;
  413   		}
  414   	}
  415   
  416   	private void logClosePreparedStatement() {
  417   		if ( log.isDebugEnabled() ) {
  418   			log.debug( "about to close PreparedStatement" + preparedStatementCountsToString() );
  419   			openPreparedStatementCount--;
  420   			globalOpenPreparedStatementCount--;
  421   		}
  422   	}
  423   
  424   	private void logOpenResults() {
  425   		if ( log.isDebugEnabled() ) {
  426   			log.debug( "about to open ResultSet" + resultSetCountsToString() );
  427   			openResultSetCount++;
  428   			globalOpenResultSetCount++;
  429   		}
  430   	}
  431   	private void logCloseResults() {
  432   		if ( log.isDebugEnabled() ) {
  433   			log.debug( "about to close ResultSet" + resultSetCountsToString() );
  434   			openResultSetCount--;
  435   			globalOpenResultSetCount--;
  436   		}
  437   	}
  438   
  439   	protected SessionFactoryImplementor getFactory() {
  440   		return factory;
  441   	}
  442   
  443   	private void log(String sql) {
  444   		factory.getSettings().getSqlStatementLogger().logStatement( sql, FormatStyle.BASIC );
  445   	}
  446   
  447   	private PreparedStatement getPreparedStatement(
  448   			final Connection conn,
  449   	        final String sql,
  450   	        final boolean scrollable,
  451   	        final ScrollMode scrollMode) throws SQLException {
  452   		return getPreparedStatement(
  453   				conn,
  454   		        sql,
  455   		        scrollable,
  456   		        false,
  457   		        null,
  458   		        scrollMode,
  459   		        false
  460   		);
  461   	}
  462   
  463   	private CallableStatement getCallableStatement(
  464   			final Connection conn,
  465   	        String sql,
  466   	        boolean scrollable) throws SQLException {
  467   		if ( scrollable && !factory.getSettings().isScrollableResultSetsEnabled() ) {
  468   			throw new AssertionFailure("scrollable result sets are not enabled");
  469   		}
  470   
  471   		sql = getSQL( sql );
  472   		log( sql );
  473   
  474   		log.trace("preparing callable statement");
  475   		if ( scrollable ) {
  476   			return conn.prepareCall(
  477   					sql,
  478   			        ResultSet.TYPE_SCROLL_INSENSITIVE,
  479   			        ResultSet.CONCUR_READ_ONLY
  480   			);
  481   		}
  482   		else {
  483   			return conn.prepareCall( sql );
  484   		}
  485   	}
  486   
  487   	private String getSQL(String sql) {
  488   		sql = interceptor.onPrepareStatement( sql );
  489   		if ( sql==null || sql.length() == 0 ) {
  490   			throw new AssertionFailure( "Interceptor.onPrepareStatement() returned null or empty string." );
  491   		}
  492   		return sql;
  493   	}
  494   
  495   	private PreparedStatement getPreparedStatement(
  496   			final Connection conn,
  497   	        String sql,
  498   	        boolean scrollable,
  499   	        final boolean useGetGeneratedKeys,
  500   	        final String[] namedGeneratedKeys,
  501   	        final ScrollMode scrollMode,
  502   	        final boolean callable) throws SQLException {
  503   		if ( scrollable && !factory.getSettings().isScrollableResultSetsEnabled() ) {
  504   			throw new AssertionFailure("scrollable result sets are not enabled");
  505   		}
  506   		if ( useGetGeneratedKeys && !factory.getSettings().isGetGeneratedKeysEnabled() ) {
  507   			throw new AssertionFailure("getGeneratedKeys() support is not enabled");
  508   		}
  509   
  510   		sql = getSQL( sql );
  511   		log( sql );
  512   
  513   		log.trace( "preparing statement" );
  514   		PreparedStatement result;
  515   		if ( scrollable ) {
  516   			if ( callable ) {
  517   				result = conn.prepareCall( sql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY );
  518   			}
  519   			else {
  520   				result = conn.prepareStatement( sql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY );
  521   			}
  522   		}
  523   		else if ( useGetGeneratedKeys ) {
  524   			result = conn.prepareStatement( sql, PreparedStatement.RETURN_GENERATED_KEYS );
  525   		}
  526   		else if ( namedGeneratedKeys != null ) {
  527   			result = conn.prepareStatement( sql, namedGeneratedKeys );
  528   		}
  529   		else {
  530   			if ( callable ) {
  531   				result = conn.prepareCall( sql );
  532   			}
  533   			else {
  534   				result = conn.prepareStatement( sql );
  535   			}
  536   		}
  537   
  538   		setTimeout( result );
  539   
  540   		if ( factory.getStatistics().isStatisticsEnabled() ) {
  541   			factory.getStatisticsImplementor().prepareStatement();
  542   		}
  543   
  544   		return result;
  545   
  546   	}
  547   
  548   	private void setTimeout(PreparedStatement result) throws SQLException {
  549   		if ( isTransactionTimeoutSet ) {
  550   			int timeout = (int) ( transactionTimeout - ( System.currentTimeMillis() / 1000 ) );
  551   			if (timeout<=0) {
  552   				throw new TransactionException("transaction timeout expired");
  553   			}
  554   			else {
  555   				result.setQueryTimeout(timeout);
  556   			}
  557   		}
  558   	}
  559   
  560   	private void closePreparedStatement(PreparedStatement ps) throws SQLException {
  561   		try {
  562   			log.trace("closing statement");
  563   			ps.close();
  564   			if ( factory.getStatistics().isStatisticsEnabled() ) {
  565   				factory.getStatisticsImplementor().closeStatement();
  566   			}
  567   		}
  568   		finally {
  569   			if ( !releasing ) {
  570   				// If we are in the process of releasing, no sense
  571   				// checking for aggressive-release possibility.
  572   				connectionManager.afterStatement();
  573   			}
  574   		}
  575   	}
  576   
  577   	private void setStatementFetchSize(PreparedStatement statement) throws SQLException {
  578   		Integer statementFetchSize = factory.getSettings().getJdbcFetchSize();
  579   		if ( statementFetchSize!=null ) {
  580   			statement.setFetchSize( statementFetchSize.intValue() );
  581   		}
  582   	}
  583   
  584   	public Connection openConnection() throws HibernateException {
  585   		log.debug("opening JDBC connection");
  586   		try {
  587   			return factory.getConnectionProvider().getConnection();
  588   		}
  589   		catch (SQLException sqle) {
  590   			throw JDBCExceptionHelper.convert(
  591   					factory.getSQLExceptionConverter(),
  592   			        sqle,
  593   			        "Cannot open connection"
  594   				);
  595   		}
  596   	}
  597   
  598   	public void closeConnection(Connection conn) throws HibernateException {
  599   		if ( conn == null ) {
  600   			log.debug( "found null connection on AbstractBatcher#closeConnection" );
  601   			// EARLY EXIT!!!!
  602   			return;
  603   		}
  604   
  605   		if ( log.isDebugEnabled() ) {
  606   			log.debug( "closing JDBC connection" + preparedStatementCountsToString() + resultSetCountsToString() );
  607   		}
  608   
  609   		try {
  610   			if ( !conn.isClosed() ) {
  611   				JDBCExceptionReporter.logAndClearWarnings( conn );
  612   			}
  613   			factory.getConnectionProvider().closeConnection( conn );
  614   		}
  615   		catch ( SQLException sqle ) {
  616   			throw JDBCExceptionHelper.convert( factory.getSQLExceptionConverter(), sqle, "Cannot close connection" );
  617   		}
  618   	}
  619   
  620   	public void cancelLastQuery() throws HibernateException {
  621   		try {
  622   			if (lastQuery!=null) lastQuery.cancel();
  623   		}
  624   		catch (SQLException sqle) {
  625   			throw JDBCExceptionHelper.convert(
  626   					factory.getSQLExceptionConverter(),
  627   			        sqle,
  628   			        "Cannot cancel query"
  629   				);
  630   		}
  631   	}
  632   
  633   	public boolean hasOpenResources() {
  634   		return resultSetsToClose.size() > 0 || statementsToClose.size() > 0;
  635   	}
  636   
  637   	public String openResourceStatsAsString() {
  638   		return preparedStatementCountsToString() + resultSetCountsToString();
  639   	}
  640   
  641   }
  642   
  643   
  644   
  645   
  646   
  647   

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » jdbc » [javadoc | source]