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 }