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 }