Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/hibernate/jdbc/ConnectionManager.java


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