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.io.IOException;
   28   import java.io.ObjectInputStream;
   29   import java.io.ObjectOutputStream;
   30   import java.io.Serializable;
   31   import java.sql.Connection;
   32   import java.sql.SQLException;
   33   
   34   import org.slf4j.Logger;
   35   import org.slf4j.LoggerFactory;
   36   import org.hibernate.ConnectionReleaseMode;
   37   import org.hibernate.HibernateException;
   38   import org.hibernate.Interceptor;
   39   import org.hibernate.engine.SessionFactoryImplementor;
   40   import org.hibernate.exception.JDBCExceptionHelper;
   41   import org.hibernate.util.JDBCExceptionReporter;
   42   
   43   /**
   44    * Encapsulates JDBC Connection management logic needed by Hibernate.
   45    * <p/>
   46    * The lifecycle is intended to span a logical series of interactions with the
   47    * database.  Internally, this means the the lifecycle of the Session.
   48    *
   49    * @author Steve Ebersole
   50    */
   51   public class ConnectionManager implements Serializable {
   52   
   53   	private static final Logger log = LoggerFactory.getLogger( ConnectionManager.class );
   54   
   55   	public static interface Callback {
   56   		public void connectionOpened();
   57   		public void connectionCleanedUp();
   58   		public boolean isTransactionInProgress();
   59   	}
   60   
   61   	private transient SessionFactoryImplementor factory;
   62   	private final Callback callback;
   63   
   64   	private final ConnectionReleaseMode releaseMode;
   65   	private transient Connection connection;
   66   	private transient Connection borrowedConnection;
   67   
   68   	private final boolean wasConnectionSupplied;
   69   	private transient Batcher batcher;
   70   	private transient Interceptor interceptor;
   71   	private boolean isClosed;
   72   	private transient boolean isFlushing;
   73    
   74   	/**
   75   	 * Constructs a ConnectionManager.
   76   	 * <p/>
   77   	 * This is the form used internally.
   78   	 * 
   79   	 * @param factory The SessionFactory.
   80   	 * @param callback An observer for internal state change.
   81   	 * @param releaseMode The mode by which to release JDBC connections.
   82   	 * @param connection An externally supplied connection.
   83   	 */ 
   84   	public ConnectionManager(
   85   	        SessionFactoryImplementor factory,
   86   	        Callback callback,
   87   	        ConnectionReleaseMode releaseMode,
   88   	        Connection connection,
   89   	        Interceptor interceptor) {
   90   		this.factory = factory;
   91   		this.callback = callback;
   92   
   93   		this.interceptor = interceptor;
   94   		this.batcher = factory.getSettings().getBatcherFactory().createBatcher( this, interceptor );
   95   
   96   		this.connection = connection;
   97   		wasConnectionSupplied = ( connection != null );
   98   
   99   		this.releaseMode = wasConnectionSupplied ? ConnectionReleaseMode.ON_CLOSE : releaseMode;
  100   	}
  101   
  102   	/**
  103   	 * Private constructor used exclusively from custom serialization
  104   	 */
  105   	private ConnectionManager(
  106   	        SessionFactoryImplementor factory,
  107   	        Callback callback,
  108   	        ConnectionReleaseMode releaseMode,
  109   	        Interceptor interceptor,
  110   	        boolean wasConnectionSupplied,
  111   	        boolean isClosed) {
  112   		this.factory = factory;
  113   		this.callback = callback;
  114   
  115   		this.interceptor = interceptor;
  116   		this.batcher = factory.getSettings().getBatcherFactory().createBatcher( this, interceptor );
  117   
  118   		this.wasConnectionSupplied = wasConnectionSupplied;
  119   		this.isClosed = isClosed;
  120   		this.releaseMode = wasConnectionSupplied ? ConnectionReleaseMode.ON_CLOSE : releaseMode;
  121   	}
  122   
  123   	/**
  124   	 * The session factory.
  125   	 *
  126   	 * @return the session factory.
  127   	 */
  128   	public SessionFactoryImplementor getFactory() {
  129   		return factory;
  130   	}
  131   
  132   	/**
  133   	 * The batcher managed by this ConnectionManager.
  134   	 *
  135   	 * @return The batcher.
  136   	 */
  137   	public Batcher getBatcher() {
  138   		return batcher;
  139   	}
  140   
  141   	/**
  142   	 * Was the connection being used here supplied by the user?
  143   	 *
  144   	 * @return True if the user supplied the JDBC connection; false otherwise
  145   	 */
  146   	public boolean isSuppliedConnection() {
  147   		return wasConnectionSupplied;
  148   	}
  149   
  150   	/**
  151   	 * Retrieves the connection currently managed by this ConnectionManager.
  152   	 * <p/>
  153   	 * Note, that we may need to obtain a connection to return here if a
  154   	 * connection has either not yet been obtained (non-UserSuppliedConnectionProvider)
  155   	 * or has previously been aggressively released (if supported in this environment).
  156   	 *
  157   	 * @return The current Connection.
  158   	 *
  159   	 * @throws HibernateException Indicates a connection is currently not
  160   	 * available (we are currently manually disconnected).
  161   	 */
  162   	public Connection getConnection() throws HibernateException {
  163   		if ( isClosed ) {
  164   			throw new HibernateException( "connection manager has been closed" );
  165   		}
  166   		if ( connection == null  ) {
  167   			openConnection();
  168   		}
  169   		return connection;
  170   	}
  171   
  172   	public boolean hasBorrowedConnection() {
  173   		// used from testsuite
  174   		return borrowedConnection != null;
  175   	}
  176   
  177   	public Connection borrowConnection() {
  178   		if ( isClosed ) {
  179   			throw new HibernateException( "connection manager has been closed" );
  180   		}
  181   		if ( isSuppliedConnection() ) {
  182   			return connection;
  183   		}
  184   		else {
  185   			if ( borrowedConnection == null ) {
  186   				borrowedConnection = BorrowedConnectionProxy.generateProxy( this );
  187   			}
  188   			return borrowedConnection;
  189   		}
  190   	}
  191   
  192   	public void releaseBorrowedConnection() {
  193   		if ( borrowedConnection != null ) {
  194   			try {
  195   				BorrowedConnectionProxy.renderUnuseable( borrowedConnection );
  196   			}
  197   			finally {
  198   				borrowedConnection = null;
  199   			}
  200   		}
  201   	}
  202   
  203   	/**
  204   	 * Is the connection considered "auto-commit"?
  205   	 *
  206   	 * @return True if we either do not have a connection, or the connection
  207   	 * really is in auto-commit mode.
  208   	 *
  209   	 * @throws SQLException Can be thrown by the Connection.isAutoCommit() check.
  210   	 */
  211   	public boolean isAutoCommit() throws SQLException {
  212   		return connection == null 
  213   			|| connection.isClosed()
  214   			|| connection.getAutoCommit();
  215   	}
  216   
  217   	/**
  218   	 * Will connections be released after each statement execution?
  219   	 * <p/>
  220   	 * Connections will be released after each statement if either:<ul>
  221   	 * <li>the defined release-mode is {@link ConnectionReleaseMode#AFTER_STATEMENT}; or
  222   	 * <li>the defined release-mode is {@link ConnectionReleaseMode#AFTER_TRANSACTION} but
  223   	 * we are in auto-commit mode.
  224   	 * <p/>
  225   	 * release-mode = {@link ConnectionReleaseMode#ON_CLOSE} should [b]never[/b] release
  226   	 * a connection.
  227   	 *
  228   	 * @return True if the connections will be released after each statement; false otherwise.
  229   	 */
  230   	public boolean isAggressiveRelease() {
  231   		if ( releaseMode == ConnectionReleaseMode.AFTER_STATEMENT ) {
  232   			return true;
  233   		}
  234   		else if ( releaseMode == ConnectionReleaseMode.AFTER_TRANSACTION ) {
  235   			boolean inAutoCommitState;
  236   			try {
  237   				inAutoCommitState = isAutoCommit()&& !callback.isTransactionInProgress();
  238   			}
  239   			catch( SQLException e ) {
  240   				// assume we are in an auto-commit state
  241   				inAutoCommitState = true;
  242   			}
  243   			return inAutoCommitState;
  244   		}
  245   		return false;
  246   	}
  247   
  248   	/**
  249   	 * Modified version of {@link #isAggressiveRelease} which does not force a
  250   	 * transaction check.  This is solely used from our {@link #afterTransaction}
  251   	 * callback, so no need to do the check; plus it seems to cause problems on
  252   	 * websphere (god i love websphere ;)
  253   	 * </p>
  254   	 * It uses this information to decide if an aggressive release was skipped
  255   	 * do to open resources, and if so forces a release.
  256   	 *
  257   	 * @return True if the connections will be released after each statement; false otherwise.
  258   	 */
  259   	private boolean isAggressiveReleaseNoTransactionCheck() {
  260   		if ( releaseMode == ConnectionReleaseMode.AFTER_STATEMENT ) {
  261   			return true;
  262   		}
  263   		else {
  264   			boolean inAutoCommitState;
  265   			try {
  266   				inAutoCommitState = isAutoCommit();
  267   			}
  268   			catch( SQLException e ) {
  269   				// assume we are in an auto-commit state
  270   				inAutoCommitState = true;
  271   			}
  272   			return releaseMode == ConnectionReleaseMode.AFTER_TRANSACTION && inAutoCommitState;
  273   		}
  274   	}
  275   
  276   	/**
  277   	 * Is this ConnectionManager instance "logically" connected.  Meaning
  278   	 * do we either have a cached connection available or do we have the
  279   	 * ability to obtain a connection on demand.
  280   	 *
  281   	 * @return True if logically connected; false otherwise.
  282   	 */
  283   	public boolean isCurrentlyConnected() {
  284   		return wasConnectionSupplied ? connection != null : !isClosed;
  285   	}
  286   
  287   	/**
  288   	 * To be called after execution of each JDBC statement.  Used to
  289   	 * conditionally release the JDBC connection aggressively if
  290   	 * the configured release mode indicates.
  291   	 */
  292   	public void afterStatement() {
  293   		if ( isAggressiveRelease() ) {
  294   			if ( isFlushing ) {
  295   				log.debug( "skipping aggressive-release due to flush cycle" );
  296   			}
  297   			else if ( batcher.hasOpenResources() ) {
  298   				log.debug( "skipping aggresive-release due to open resources on batcher" );
  299   			}
  300   			else if ( borrowedConnection != null ) {
  301   				log.debug( "skipping aggresive-release due to borrowed connection" );
  302   			}
  303   			else {
  304   				aggressiveRelease();
  305   			}
  306   		}
  307   	}
  308   
  309   	/**
  310   	 * To be called after local transaction completion.  Used to conditionally
  311   	 * release the JDBC connection aggressively if the configured release mode
  312   	 * indicates.
  313   	 */
  314   	public void afterTransaction() {
  315   		if ( isAfterTransactionRelease() ) {
  316   			aggressiveRelease();
  317   		}
  318   		else if ( isAggressiveReleaseNoTransactionCheck() && batcher.hasOpenResources() ) {
  319   			log.info( "forcing batcher resource cleanup on transaction completion; forgot to close ScrollableResults/Iterator?" );
  320   			batcher.closeStatements();
  321   			aggressiveRelease();
  322   		}
  323   		else if ( isOnCloseRelease() ) {
  324   			// log a message about potential connection leaks
  325   			log.debug( "transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!" );
  326   		}
  327   		batcher.unsetTransactionTimeout();
  328   	}
  329   
  330   	private boolean isAfterTransactionRelease() {
  331   		return releaseMode == ConnectionReleaseMode.AFTER_TRANSACTION;
  332   	}
  333   
  334   	private boolean isOnCloseRelease() {
  335   		return releaseMode == ConnectionReleaseMode.ON_CLOSE;
  336   	}
  337   
  338   	/**
  339   	 * To be called after Session completion.  Used to release the JDBC
  340   	 * connection.
  341   	 *
  342   	 * @return The connection mantained here at time of close.  Null if
  343   	 * there was no connection cached internally.
  344   	 */
  345   	public Connection close() {
  346   		try {
  347   			return cleanup();
  348   		}
  349   		finally {
  350   			isClosed = true;
  351   		}
  352   	}
  353   
  354   	/**
  355   	 * Manually disconnect the underlying JDBC Connection.  The assumption here
  356   	 * is that the manager will be reconnected at a later point in time.
  357   	 *
  358   	 * @return The connection mantained here at time of disconnect.  Null if
  359   	 * there was no connection cached internally.
  360   	 */
  361   	public Connection manualDisconnect() {
  362   		return cleanup();
  363   	}
  364   
  365   	/**
  366   	 * Manually reconnect the underlying JDBC Connection.  Should be called at
  367   	 * some point after manualDisconnect().
  368   	 * <p/>
  369   	 * This form is used for ConnectionProvider-supplied connections.
  370   	 */
  371   	public void manualReconnect() {
  372   	}
  373   
  374   	/**
  375   	 * Manually reconnect the underlying JDBC Connection.  Should be called at
  376   	 * some point after manualDisconnect().
  377   	 * <p/>
  378   	 * This form is used for user-supplied connections.
  379   	 */
  380   	public void manualReconnect(Connection suppliedConnection) {
  381   		this.connection = suppliedConnection;
  382   	}
  383   
  384   	/**
  385   	 * Releases the Connection and cleans up any resources associated with
  386   	 * that Connection.  This is intended for use:
  387   	 * 1) at the end of the session
  388   	 * 2) on a manual disconnect of the session
  389   	 * 3) from afterTransaction(), in the case of skipped aggressive releasing
  390   	 *
  391   	 * @return The released connection.
  392   	 * @throws HibernateException
  393   	 */
  394   	private Connection cleanup() throws HibernateException {
  395   		releaseBorrowedConnection();
  396   
  397   		if ( connection == null ) {
  398   			log.trace( "connection already null in cleanup : no action");
  399   			return null;
  400   		}
  401   
  402   		try {
  403   			log.trace( "performing cleanup" );
  404   
  405   			batcher.closeStatements();
  406   			Connection c = null;
  407   			if ( !wasConnectionSupplied ) {
  408   				closeConnection();
  409   			}
  410   			else {
  411   				c = connection;
  412   			}
  413   			connection = null;
  414   			return c;
  415   		}
  416   		finally {
  417   			callback.connectionCleanedUp();
  418   		}
  419   	}
  420   
  421   	/**
  422   	 * Performs actions required to perform an aggressive release of the
  423   	 * JDBC Connection.
  424   	 */
  425   	private void aggressiveRelease() {
  426   		if ( !wasConnectionSupplied ) {
  427   			log.debug( "aggressively releasing JDBC connection" );
  428   			if ( connection != null ) {
  429   				closeConnection();
  430   			}
  431   		}
  432   	}
  433   
  434   	/**
  435   	 * Pysically opens a JDBC Connection.
  436   	 *
  437   	 * @throws HibernateException
  438   	 */
  439   	private void openConnection() throws HibernateException {
  440   		if ( connection != null ) {
  441   			return;
  442   		}
  443   
  444   		log.debug("opening JDBC connection");
  445   		try {
  446   			connection = factory.getConnectionProvider().getConnection();
  447   		}
  448   		catch (SQLException sqle) {
  449   			throw JDBCExceptionHelper.convert(
  450   					factory.getSQLExceptionConverter(),
  451   					sqle,
  452   					"Cannot open connection"
  453   				);
  454   		}
  455   
  456   		callback.connectionOpened(); // register synch; stats.connect()
  457   	}
  458   
  459   	/**
  460   	 * Physically closes the JDBC Connection.
  461   	 */
  462   	private void closeConnection() {
  463   		if ( log.isDebugEnabled() ) {
  464   			log.debug(
  465   					"releasing JDBC connection [" +
  466   					batcher.openResourceStatsAsString() + "]"
  467   				);
  468   		}
  469   
  470   		try {
  471   			if ( !connection.isClosed() ) {
  472   				JDBCExceptionReporter.logAndClearWarnings( connection );
  473   			}
  474   			factory.getConnectionProvider().closeConnection( connection );
  475   			connection = null;
  476   		}
  477   		catch (SQLException sqle) {
  478   			throw JDBCExceptionHelper.convert( 
  479   					factory.getSQLExceptionConverter(), 
  480   					sqle, 
  481   					"Cannot release connection"
  482   				);
  483   		}
  484   	}
  485   
  486   	/**
  487   	 * Callback to let us know that a flush is beginning.  We use this fact
  488   	 * to temporarily circumvent aggressive connection releasing until after
  489   	 * the flush cycle is complete {@link #flushEnding()}
  490   	 */
  491   	public void flushBeginning() {
  492   		log.trace( "registering flush begin" );
  493   		isFlushing = true;
  494   	}
  495   
  496   	/**
  497   	 * Callback to let us know that a flush is ending.  We use this fact to
  498   	 * stop circumventing aggressive releasing connections.
  499   	 */
  500   	public void flushEnding() {
  501   		log.trace( "registering flush end" );
  502   		isFlushing = false;
  503   		afterStatement();
  504   	}
  505   
  506   	public boolean isReadyForSerialization() {
  507   		return wasConnectionSupplied ? connection == null : !batcher.hasOpenResources();
  508   	}
  509   
  510   	/**
  511   	 * Used during serialization.
  512   	 *
  513   	 * @param oos The stream to which we are being written.
  514   	 * @throws IOException Indicates an I/O error writing to the stream
  515   	 */
  516   	private void writeObject(ObjectOutputStream oos) throws IOException {
  517   		if ( !isReadyForSerialization() ) {
  518   			throw new IllegalStateException( "Cannot serialize a ConnectionManager while connected" );
  519   		}
  520   
  521   		oos.writeObject( factory );
  522   		oos.writeObject( interceptor );
  523   		oos.defaultWriteObject();
  524   	}
  525   
  526   	/**
  527   	 * Used during deserialization.
  528   	 *
  529   	 * @param ois The stream from which we are being read.
  530   	 * @throws IOException Indicates an I/O error reading the stream
  531   	 * @throws ClassNotFoundException Indicates resource class resolution.
  532   	 */
  533   	private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
  534   		factory = (SessionFactoryImplementor) ois.readObject();
  535   		interceptor = (Interceptor) ois.readObject();
  536   		ois.defaultReadObject();
  537   
  538   		this.batcher = factory.getSettings().getBatcherFactory().createBatcher( this, interceptor );
  539   	}
  540   
  541   	public void serialize(ObjectOutputStream oos) throws IOException {
  542   		oos.writeBoolean( wasConnectionSupplied );
  543   		oos.writeBoolean( isClosed );
  544   	}
  545   
  546   	public static ConnectionManager deserialize(
  547   			ObjectInputStream ois,
  548   	        SessionFactoryImplementor factory,
  549   	        Interceptor interceptor,
  550   	        ConnectionReleaseMode connectionReleaseMode,
  551   	        JDBCContext jdbcContext) throws IOException {
  552   		return new ConnectionManager(
  553   				factory,
  554   		        jdbcContext,
  555   		        connectionReleaseMode,
  556   		        interceptor,
  557   		        ois.readBoolean(),
  558   		        ois.readBoolean()
  559   		);
  560   	}
  561   
  562   }

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