1 /* 2 * Copyright 2002-2012 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.orm.hibernate3; 18 19 import java.util.HashMap; 20 import java.util.LinkedHashSet; 21 import java.util.Map; 22 import java.util.Set; 23 import javax.sql.DataSource; 24 import javax.transaction.Status; 25 import javax.transaction.Transaction; 26 import javax.transaction.TransactionManager; 27 28 import org.apache.commons.logging.Log; 29 import org.apache.commons.logging.LogFactory; 30 import org.hibernate.Criteria; 31 import org.hibernate.FlushMode; 32 import org.hibernate.HibernateException; 33 import org.hibernate.Interceptor; 34 import org.hibernate.JDBCException; 35 import org.hibernate.NonUniqueObjectException; 36 import org.hibernate.NonUniqueResultException; 37 import org.hibernate.ObjectDeletedException; 38 import org.hibernate.PersistentObjectException; 39 import org.hibernate.PropertyValueException; 40 import org.hibernate.Query; 41 import org.hibernate.QueryException; 42 import org.hibernate.Session; 43 import org.hibernate.SessionFactory; 44 import org.hibernate.StaleObjectStateException; 45 import org.hibernate.StaleStateException; 46 import org.hibernate.TransientObjectException; 47 import org.hibernate.UnresolvableObjectException; 48 import org.hibernate.WrongClassException; 49 import org.hibernate.connection.ConnectionProvider; 50 import org.hibernate.engine.SessionFactoryImplementor; 51 import org.hibernate.exception.ConstraintViolationException; 52 import org.hibernate.exception.DataException; 53 import org.hibernate.exception.JDBCConnectionException; 54 import org.hibernate.exception.LockAcquisitionException; 55 import org.hibernate.exception.SQLGrammarException; 56 57 import org.springframework.core.NamedThreadLocal; 58 import org.springframework.dao.CannotAcquireLockException; 59 import org.springframework.dao.DataAccessException; 60 import org.springframework.dao.DataAccessResourceFailureException; 61 import org.springframework.dao.DataIntegrityViolationException; 62 import org.springframework.dao.DuplicateKeyException; 63 import org.springframework.dao.IncorrectResultSizeDataAccessException; 64 import org.springframework.dao.InvalidDataAccessApiUsageException; 65 import org.springframework.dao.InvalidDataAccessResourceUsageException; 66 import org.springframework.jdbc.datasource.DataSourceUtils; 67 import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; 68 import org.springframework.jdbc.support.SQLExceptionTranslator; 69 import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; 70 import org.springframework.transaction.jta.SpringJtaSynchronizationAdapter; 71 import org.springframework.transaction.support.TransactionSynchronizationManager; 72 import org.springframework.util.Assert; 73 74 /** 75 * Helper class featuring methods for Hibernate Session handling, 76 * allowing for reuse of Hibernate Session instances within transactions. 77 * Also provides support for exception translation. 78 * 79 * <p>Supports synchronization with both Spring-managed JTA transactions 80 * (see {@link org.springframework.transaction.jta.JtaTransactionManager}) 81 * and non-Spring JTA transactions (i.e. plain JTA or EJB CMT), 82 * transparently providing transaction-scoped Hibernate Sessions. 83 * Note that for non-Spring JTA transactions, a JTA TransactionManagerLookup 84 * has to be specified in the Hibernate configuration. 85 * 86 * <p>Used internally by {@link HibernateTemplate}, {@link HibernateInterceptor} 87 * and {@link HibernateTransactionManager}. Can also be used directly in 88 * application code. 89 * 90 * @author Juergen Hoeller 91 * @since 1.2 92 * @see #getSession 93 * @see #releaseSession 94 * @see HibernateTransactionManager 95 * @see org.springframework.transaction.jta.JtaTransactionManager 96 * @see org.springframework.transaction.support.TransactionSynchronizationManager 97 */ 98 public abstract class SessionFactoryUtils { 99 100 /** 101 * Order value for TransactionSynchronization objects that clean up Hibernate Sessions. 102 * Returns <code>DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100</code> 103 * to execute Session cleanup before JDBC Connection cleanup, if any. 104 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER 105 */ 106 public static final int SESSION_SYNCHRONIZATION_ORDER = 107 DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100; 108 109 static final Log logger = LogFactory.getLog(SessionFactoryUtils.class); 110 111 private static final ThreadLocal<Map<SessionFactory, Set<Session>>> deferredCloseHolder = 112 new NamedThreadLocal<Map<SessionFactory, Set<Session>>>("Hibernate Sessions registered for deferred close"); 113 114 115 /** 116 * Determine the DataSource of the given SessionFactory. 117 * @param sessionFactory the SessionFactory to check 118 * @return the DataSource, or <code>null</code> if none found 119 * @see org.hibernate.engine.SessionFactoryImplementor#getConnectionProvider 120 * @see LocalDataSourceConnectionProvider 121 */ getDataSource(SessionFactory sessionFactory)122 public static DataSource getDataSource(SessionFactory sessionFactory) { 123 if (sessionFactory instanceof SessionFactoryImplementor) { 124 ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getConnectionProvider(); 125 if (cp instanceof LocalDataSourceConnectionProvider) { 126 return ((LocalDataSourceConnectionProvider) cp).getDataSource(); 127 } 128 } 129 return null; 130 } 131 132 /** 133 * Create an appropriate SQLExceptionTranslator for the given SessionFactory. 134 * If a DataSource is found, a SQLErrorCodeSQLExceptionTranslator for the DataSource 135 * is created; else, a SQLStateSQLExceptionTranslator as fallback. 136 * @param sessionFactory the SessionFactory to create the translator for 137 * @return the SQLExceptionTranslator 138 * @see #getDataSource 139 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator 140 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator 141 */ newJdbcExceptionTranslator(SessionFactory sessionFactory)142 public static SQLExceptionTranslator newJdbcExceptionTranslator(SessionFactory sessionFactory) { 143 DataSource ds = getDataSource(sessionFactory); 144 if (ds != null) { 145 return new SQLErrorCodeSQLExceptionTranslator(ds); 146 } 147 return new SQLStateSQLExceptionTranslator(); 148 } 149 150 /** 151 * Try to retrieve the JTA TransactionManager from the given SessionFactory 152 * and/or Session. Check the passed-in SessionFactory for implementing 153 * SessionFactoryImplementor (the usual case), falling back to the 154 * SessionFactory reference that the Session itself carries. 155 * @param sessionFactory Hibernate SessionFactory 156 * @param session Hibernate Session (can also be <code>null</code>) 157 * @return the JTA TransactionManager, if any 158 * @see javax.transaction.TransactionManager 159 * @see SessionFactoryImplementor#getTransactionManager 160 * @see Session#getSessionFactory 161 * @see org.hibernate.impl.SessionFactoryImpl 162 */ getJtaTransactionManager(SessionFactory sessionFactory, Session session)163 public static TransactionManager getJtaTransactionManager(SessionFactory sessionFactory, Session session) { 164 SessionFactoryImplementor sessionFactoryImpl = null; 165 if (sessionFactory instanceof SessionFactoryImplementor) { 166 sessionFactoryImpl = ((SessionFactoryImplementor) sessionFactory); 167 } 168 else if (session != null) { 169 SessionFactory internalFactory = session.getSessionFactory(); 170 if (internalFactory instanceof SessionFactoryImplementor) { 171 sessionFactoryImpl = (SessionFactoryImplementor) internalFactory; 172 } 173 } 174 return (sessionFactoryImpl != null ? sessionFactoryImpl.getTransactionManager() : null); 175 } 176 177 178 /** 179 * Get a Hibernate Session for the given SessionFactory. Is aware of and will 180 * return any existing corresponding Session bound to the current thread, for 181 * example when using {@link HibernateTransactionManager}. Will create a new 182 * Session otherwise, if "allowCreate" is <code>true</code>. 183 * <p>This is the <code>getSession</code> method used by typical data access code, 184 * in combination with <code>releaseSession</code> called when done with 185 * the Session. Note that HibernateTemplate allows to write data access code 186 * without caring about such resource handling. 187 * @param sessionFactory Hibernate SessionFactory to create the session with 188 * @param allowCreate whether a non-transactional Session should be created 189 * when no transactional Session can be found for the current thread 190 * @return the Hibernate Session 191 * @throws DataAccessResourceFailureException if the Session couldn't be created 192 * @throws IllegalStateException if no thread-bound Session found and 193 * "allowCreate" is <code>false</code> 194 * @see #getSession(SessionFactory, Interceptor, SQLExceptionTranslator) 195 * @see #releaseSession 196 * @see HibernateTemplate 197 */ getSession(SessionFactory sessionFactory, boolean allowCreate)198 public static Session getSession(SessionFactory sessionFactory, boolean allowCreate) 199 throws DataAccessResourceFailureException, IllegalStateException { 200 201 try { 202 return doGetSession(sessionFactory, null, null, allowCreate); 203 } 204 catch (HibernateException ex) { 205 throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); 206 } 207 } 208 209 /** 210 * Get a Hibernate Session for the given SessionFactory. Is aware of and will 211 * return any existing corresponding Session bound to the current thread, for 212 * example when using {@link HibernateTransactionManager}. Will always create 213 * a new Session otherwise. 214 * <p>Supports setting a Session-level Hibernate entity interceptor that allows 215 * to inspect and change property values before writing to and reading from the 216 * database. Such an interceptor can also be set at the SessionFactory level 217 * (i.e. on LocalSessionFactoryBean), on HibernateTransactionManager, or on 218 * HibernateInterceptor/HibernateTemplate. 219 * @param sessionFactory Hibernate SessionFactory to create the session with 220 * @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none 221 * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the 222 * Session on transaction synchronization (may be <code>null</code>; only used 223 * when actually registering a transaction synchronization) 224 * @return the Hibernate Session 225 * @throws DataAccessResourceFailureException if the Session couldn't be created 226 * @see LocalSessionFactoryBean#setEntityInterceptor 227 * @see HibernateInterceptor#setEntityInterceptor 228 * @see HibernateTemplate#setEntityInterceptor 229 */ getSession( SessionFactory sessionFactory, Interceptor entityInterceptor, SQLExceptionTranslator jdbcExceptionTranslator)230 public static Session getSession( 231 SessionFactory sessionFactory, Interceptor entityInterceptor, 232 SQLExceptionTranslator jdbcExceptionTranslator) throws DataAccessResourceFailureException { 233 234 try { 235 return doGetSession(sessionFactory, entityInterceptor, jdbcExceptionTranslator, true); 236 } 237 catch (HibernateException ex) { 238 throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); 239 } 240 } 241 242 /** 243 * Get a Hibernate Session for the given SessionFactory. Is aware of and will 244 * return any existing corresponding Session bound to the current thread, for 245 * example when using {@link HibernateTransactionManager}. Will create a new 246 * Session otherwise, if "allowCreate" is <code>true</code>. 247 * <p>Throws the original HibernateException, in contrast to {@link #getSession}. 248 * @param sessionFactory Hibernate SessionFactory to create the session with 249 * @param allowCreate whether a non-transactional Session should be created 250 * when no transactional Session can be found for the current thread 251 * @return the Hibernate Session 252 * @throws HibernateException if the Session couldn't be created 253 * @throws IllegalStateException if no thread-bound Session found and allowCreate false 254 */ doGetSession(SessionFactory sessionFactory, boolean allowCreate)255 public static Session doGetSession(SessionFactory sessionFactory, boolean allowCreate) 256 throws HibernateException, IllegalStateException { 257 258 return doGetSession(sessionFactory, null, null, allowCreate); 259 } 260 261 /** 262 * Get a Hibernate Session for the given SessionFactory. Is aware of and will 263 * return any existing corresponding Session bound to the current thread, for 264 * example when using {@link HibernateTransactionManager}. Will create a new 265 * Session otherwise, if "allowCreate" is <code>true</code>. 266 * <p>Same as {@link #getSession}, but throwing the original HibernateException. 267 * @param sessionFactory Hibernate SessionFactory to create the session with 268 * @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none 269 * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the 270 * Session on transaction synchronization (may be <code>null</code>) 271 * @param allowCreate whether a non-transactional Session should be created 272 * when no transactional Session can be found for the current thread 273 * @return the Hibernate Session 274 * @throws HibernateException if the Session couldn't be created 275 * @throws IllegalStateException if no thread-bound Session found and 276 * "allowCreate" is <code>false</code> 277 */ doGetSession( SessionFactory sessionFactory, Interceptor entityInterceptor, SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)278 private static Session doGetSession( 279 SessionFactory sessionFactory, Interceptor entityInterceptor, 280 SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate) 281 throws HibernateException, IllegalStateException { 282 283 Assert.notNull(sessionFactory, "No SessionFactory specified"); 284 285 Object resource = TransactionSynchronizationManager.getResource(sessionFactory); 286 if (resource instanceof Session) { 287 return (Session) resource; 288 } 289 SessionHolder sessionHolder = (SessionHolder) resource; 290 if (sessionHolder != null && !sessionHolder.isEmpty()) { 291 // pre-bound Hibernate Session 292 Session session = null; 293 if (TransactionSynchronizationManager.isSynchronizationActive() && 294 sessionHolder.doesNotHoldNonDefaultSession()) { 295 // Spring transaction management is active -> 296 // register pre-bound Session with it for transactional flushing. 297 session = sessionHolder.getValidatedSession(); 298 if (session != null && !sessionHolder.isSynchronizedWithTransaction()) { 299 logger.debug("Registering Spring transaction synchronization for existing Hibernate Session"); 300 TransactionSynchronizationManager.registerSynchronization( 301 new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false)); 302 sessionHolder.setSynchronizedWithTransaction(true); 303 // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session 304 // with FlushMode.MANUAL, which needs to allow flushing within the transaction. 305 FlushMode flushMode = session.getFlushMode(); 306 if (flushMode.lessThan(FlushMode.COMMIT) && 307 !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 308 session.setFlushMode(FlushMode.AUTO); 309 sessionHolder.setPreviousFlushMode(flushMode); 310 } 311 } 312 } 313 else { 314 // No Spring transaction management active -> try JTA transaction synchronization. 315 session = getJtaSynchronizedSession(sessionHolder, sessionFactory, jdbcExceptionTranslator); 316 } 317 if (session != null) { 318 return session; 319 } 320 } 321 322 logger.debug("Opening Hibernate Session"); 323 Session session = (entityInterceptor != null ? 324 sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession()); 325 326 // Use same Session for further Hibernate actions within the transaction. 327 // Thread object will get removed by synchronization at transaction completion. 328 if (TransactionSynchronizationManager.isSynchronizationActive()) { 329 // We're within a Spring-managed transaction, possibly from JtaTransactionManager. 330 logger.debug("Registering Spring transaction synchronization for new Hibernate Session"); 331 SessionHolder holderToUse = sessionHolder; 332 if (holderToUse == null) { 333 holderToUse = new SessionHolder(session); 334 } 335 else { 336 holderToUse.addSession(session); 337 } 338 if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 339 session.setFlushMode(FlushMode.MANUAL); 340 } 341 TransactionSynchronizationManager.registerSynchronization( 342 new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true)); 343 holderToUse.setSynchronizedWithTransaction(true); 344 if (holderToUse != sessionHolder) { 345 TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse); 346 } 347 } 348 else { 349 // No Spring transaction management active -> try JTA transaction synchronization. 350 registerJtaSynchronization(session, sessionFactory, jdbcExceptionTranslator, sessionHolder); 351 } 352 353 // Check whether we are allowed to return the Session. 354 if (!allowCreate && !isSessionTransactional(session, sessionFactory)) { 355 closeSession(session); 356 throw new IllegalStateException("No Hibernate Session bound to thread, " + 357 "and configuration does not allow creation of non-transactional one here"); 358 } 359 360 return session; 361 } 362 363 /** 364 * Retrieve a Session from the given SessionHolder, potentially from a 365 * JTA transaction synchronization. 366 * @param sessionHolder the SessionHolder to check 367 * @param sessionFactory the SessionFactory to get the JTA TransactionManager from 368 * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the 369 * Session on transaction synchronization (may be <code>null</code>) 370 * @return the associated Session, if any 371 * @throws DataAccessResourceFailureException if the Session couldn't be created 372 */ getJtaSynchronizedSession( SessionHolder sessionHolder, SessionFactory sessionFactory, SQLExceptionTranslator jdbcExceptionTranslator)373 private static Session getJtaSynchronizedSession( 374 SessionHolder sessionHolder, SessionFactory sessionFactory, 375 SQLExceptionTranslator jdbcExceptionTranslator) throws DataAccessResourceFailureException { 376 377 // JTA synchronization is only possible with a javax.transaction.TransactionManager. 378 // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified 379 // in Hibernate configuration, it will contain a TransactionManager reference. 380 TransactionManager jtaTm = getJtaTransactionManager(sessionFactory, sessionHolder.getAnySession()); 381 if (jtaTm != null) { 382 // Check whether JTA transaction management is active -> 383 // fetch pre-bound Session for the current JTA transaction, if any. 384 // (just necessary for JTA transaction suspension, with an individual 385 // Hibernate Session per currently active/suspended transaction) 386 try { 387 // Look for transaction-specific Session. 388 Transaction jtaTx = jtaTm.getTransaction(); 389 if (jtaTx != null) { 390 int jtaStatus = jtaTx.getStatus(); 391 if (jtaStatus == Status.STATUS_ACTIVE || jtaStatus == Status.STATUS_MARKED_ROLLBACK) { 392 Session session = sessionHolder.getValidatedSession(jtaTx); 393 if (session == null && !sessionHolder.isSynchronizedWithTransaction()) { 394 // No transaction-specific Session found: If not already marked as 395 // synchronized with transaction, register the default thread-bound 396 // Session as JTA-transactional. If there is no default Session, 397 // we're a new inner JTA transaction with an outer one being suspended: 398 // In that case, we'll return null to trigger opening of a new Session. 399 session = sessionHolder.getValidatedSession(); 400 if (session != null) { 401 logger.debug("Registering JTA transaction synchronization for existing Hibernate Session"); 402 sessionHolder.addSession(jtaTx, session); 403 jtaTx.registerSynchronization( 404 new SpringJtaSynchronizationAdapter( 405 new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false), 406 jtaTm)); 407 sessionHolder.setSynchronizedWithTransaction(true); 408 // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session 409 // with FlushMode.NEVER, which needs to allow flushing within the transaction. 410 FlushMode flushMode = session.getFlushMode(); 411 if (flushMode.lessThan(FlushMode.COMMIT)) { 412 session.setFlushMode(FlushMode.AUTO); 413 sessionHolder.setPreviousFlushMode(flushMode); 414 } 415 } 416 } 417 return session; 418 } 419 } 420 // No transaction active -> simply return default thread-bound Session, if any 421 // (possibly from OpenSessionInViewFilter/Interceptor). 422 return sessionHolder.getValidatedSession(); 423 } 424 catch (Throwable ex) { 425 throw new DataAccessResourceFailureException("Could not check JTA transaction", ex); 426 } 427 } 428 else { 429 // No JTA TransactionManager -> simply return default thread-bound Session, if any 430 // (possibly from OpenSessionInViewFilter/Interceptor). 431 return sessionHolder.getValidatedSession(); 432 } 433 } 434 435 /** 436 * Register a JTA synchronization for the given Session, if any. 437 * @param sessionHolder the existing thread-bound SessionHolder, if any 438 * @param session the Session to register 439 * @param sessionFactory the SessionFactory that the Session was created with 440 * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the 441 * Session on transaction synchronization (may be <code>null</code>) 442 */ registerJtaSynchronization(Session session, SessionFactory sessionFactory, SQLExceptionTranslator jdbcExceptionTranslator, SessionHolder sessionHolder)443 private static void registerJtaSynchronization(Session session, SessionFactory sessionFactory, 444 SQLExceptionTranslator jdbcExceptionTranslator, SessionHolder sessionHolder) { 445 446 // JTA synchronization is only possible with a javax.transaction.TransactionManager. 447 // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified 448 // in Hibernate configuration, it will contain a TransactionManager reference. 449 TransactionManager jtaTm = getJtaTransactionManager(sessionFactory, session); 450 if (jtaTm != null) { 451 try { 452 Transaction jtaTx = jtaTm.getTransaction(); 453 if (jtaTx != null) { 454 int jtaStatus = jtaTx.getStatus(); 455 if (jtaStatus == Status.STATUS_ACTIVE || jtaStatus == Status.STATUS_MARKED_ROLLBACK) { 456 logger.debug("Registering JTA transaction synchronization for new Hibernate Session"); 457 SessionHolder holderToUse = sessionHolder; 458 // Register JTA Transaction with existing SessionHolder. 459 // Create a new SessionHolder if none existed before. 460 if (holderToUse == null) { 461 holderToUse = new SessionHolder(jtaTx, session); 462 } 463 else { 464 holderToUse.addSession(jtaTx, session); 465 } 466 jtaTx.registerSynchronization( 467 new SpringJtaSynchronizationAdapter( 468 new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true), 469 jtaTm)); 470 holderToUse.setSynchronizedWithTransaction(true); 471 if (holderToUse != sessionHolder) { 472 TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse); 473 } 474 } 475 } 476 } 477 catch (Throwable ex) { 478 throw new DataAccessResourceFailureException( 479 "Could not register synchronization with JTA TransactionManager", ex); 480 } 481 } 482 } 483 484 485 /** 486 * Get a new Hibernate Session from the given SessionFactory. 487 * Will return a new Session even if there already is a pre-bound 488 * Session for the given SessionFactory. 489 * <p>Within a transaction, this method will create a new Session 490 * that shares the transaction's JDBC Connection. More specifically, 491 * it will use the same JDBC Connection as the pre-bound Hibernate Session. 492 * @param sessionFactory Hibernate SessionFactory to create the session with 493 * @return the new Session 494 */ getNewSession(SessionFactory sessionFactory)495 public static Session getNewSession(SessionFactory sessionFactory) { 496 return getNewSession(sessionFactory, null); 497 } 498 499 /** 500 * Get a new Hibernate Session from the given SessionFactory. 501 * Will return a new Session even if there already is a pre-bound 502 * Session for the given SessionFactory. 503 * <p>Within a transaction, this method will create a new Session 504 * that shares the transaction's JDBC Connection. More specifically, 505 * it will use the same JDBC Connection as the pre-bound Hibernate Session. 506 * @param sessionFactory Hibernate SessionFactory to create the session with 507 * @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none 508 * @return the new Session 509 */ getNewSession(SessionFactory sessionFactory, Interceptor entityInterceptor)510 public static Session getNewSession(SessionFactory sessionFactory, Interceptor entityInterceptor) { 511 Assert.notNull(sessionFactory, "No SessionFactory specified"); 512 513 try { 514 SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 515 if (sessionHolder != null && !sessionHolder.isEmpty()) { 516 if (entityInterceptor != null) { 517 return sessionFactory.openSession(sessionHolder.getAnySession().connection(), entityInterceptor); 518 } 519 else { 520 return sessionFactory.openSession(sessionHolder.getAnySession().connection()); 521 } 522 } 523 else { 524 if (entityInterceptor != null) { 525 return sessionFactory.openSession(entityInterceptor); 526 } 527 else { 528 return sessionFactory.openSession(); 529 } 530 } 531 } 532 catch (HibernateException ex) { 533 throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); 534 } 535 } 536 537 538 /** 539 * Stringify the given Session for debug logging. 540 * Returns output equivalent to <code>Object.toString()</code>: 541 * the fully qualified class name + "@" + the identity hash code. 542 * <p>The sole reason why this is necessary is because Hibernate3's 543 * <code>Session.toString()</code> implementation is broken (and won't be fixed): 544 * it logs the toString representation of all persistent objects in the Session, 545 * which might lead to ConcurrentModificationExceptions if the persistent objects 546 * in turn refer to the Session (for example, for lazy loading). 547 * @param session the Hibernate Session to stringify 548 * @return the String representation of the given Session 549 */ toString(Session session)550 public static String toString(Session session) { 551 return session.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(session)); 552 } 553 554 /** 555 * Return whether there is a transactional Hibernate Session for the current thread, 556 * that is, a Session bound to the current thread by Spring's transaction facilities. 557 * @param sessionFactory Hibernate SessionFactory to check (may be <code>null</code>) 558 * @return whether there is a transactional Session for current thread 559 */ hasTransactionalSession(SessionFactory sessionFactory)560 public static boolean hasTransactionalSession(SessionFactory sessionFactory) { 561 if (sessionFactory == null) { 562 return false; 563 } 564 SessionHolder sessionHolder = 565 (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 566 return (sessionHolder != null && !sessionHolder.isEmpty()); 567 } 568 569 /** 570 * Return whether the given Hibernate Session is transactional, that is, 571 * bound to the current thread by Spring's transaction facilities. 572 * @param session the Hibernate Session to check 573 * @param sessionFactory Hibernate SessionFactory that the Session was created with 574 * (may be <code>null</code>) 575 * @return whether the Session is transactional 576 */ isSessionTransactional(Session session, SessionFactory sessionFactory)577 public static boolean isSessionTransactional(Session session, SessionFactory sessionFactory) { 578 if (sessionFactory == null) { 579 return false; 580 } 581 SessionHolder sessionHolder = 582 (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 583 return (sessionHolder != null && sessionHolder.containsSession(session)); 584 } 585 586 /** 587 * Apply the current transaction timeout, if any, to the given 588 * Hibernate Query object. 589 * @param query the Hibernate Query object 590 * @param sessionFactory Hibernate SessionFactory that the Query was created for 591 * (may be <code>null</code>) 592 * @see org.hibernate.Query#setTimeout 593 */ applyTransactionTimeout(Query query, SessionFactory sessionFactory)594 public static void applyTransactionTimeout(Query query, SessionFactory sessionFactory) { 595 Assert.notNull(query, "No Query object specified"); 596 if (sessionFactory != null) { 597 SessionHolder sessionHolder = 598 (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 599 if (sessionHolder != null && sessionHolder.hasTimeout()) { 600 query.setTimeout(sessionHolder.getTimeToLiveInSeconds()); 601 } 602 } 603 } 604 605 /** 606 * Apply the current transaction timeout, if any, to the given 607 * Hibernate Criteria object. 608 * @param criteria the Hibernate Criteria object 609 * @param sessionFactory Hibernate SessionFactory that the Criteria was created for 610 * @see org.hibernate.Criteria#setTimeout 611 */ applyTransactionTimeout(Criteria criteria, SessionFactory sessionFactory)612 public static void applyTransactionTimeout(Criteria criteria, SessionFactory sessionFactory) { 613 Assert.notNull(criteria, "No Criteria object specified"); 614 SessionHolder sessionHolder = 615 (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 616 if (sessionHolder != null && sessionHolder.hasTimeout()) { 617 criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds()); 618 } 619 } 620 621 /** 622 * Convert the given HibernateException to an appropriate exception 623 * from the <code>org.springframework.dao</code> hierarchy. 624 * @param ex HibernateException that occured 625 * @return the corresponding DataAccessException instance 626 * @see HibernateAccessor#convertHibernateAccessException 627 * @see HibernateTransactionManager#convertHibernateAccessException 628 */ convertHibernateAccessException(HibernateException ex)629 public static DataAccessException convertHibernateAccessException(HibernateException ex) { 630 if (ex instanceof JDBCConnectionException) { 631 return new DataAccessResourceFailureException(ex.getMessage(), ex); 632 } 633 if (ex instanceof SQLGrammarException) { 634 SQLGrammarException jdbcEx = (SQLGrammarException) ex; 635 return new InvalidDataAccessResourceUsageException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 636 } 637 if (ex instanceof LockAcquisitionException) { 638 LockAcquisitionException jdbcEx = (LockAcquisitionException) ex; 639 return new CannotAcquireLockException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 640 } 641 if (ex instanceof ConstraintViolationException) { 642 ConstraintViolationException jdbcEx = (ConstraintViolationException) ex; 643 return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + 644 "]; constraint [" + jdbcEx.getConstraintName() + "]", ex); 645 } 646 if (ex instanceof DataException) { 647 DataException jdbcEx = (DataException) ex; 648 return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 649 } 650 if (ex instanceof JDBCException) { 651 return new HibernateJdbcException((JDBCException) ex); 652 } 653 // end of JDBCException (subclass) handling 654 655 if (ex instanceof QueryException) { 656 return new HibernateQueryException((QueryException) ex); 657 } 658 if (ex instanceof NonUniqueResultException) { 659 return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex); 660 } 661 if (ex instanceof NonUniqueObjectException) { 662 return new DuplicateKeyException(ex.getMessage(), ex); 663 } 664 if (ex instanceof PropertyValueException) { 665 return new DataIntegrityViolationException(ex.getMessage(), ex); 666 } 667 if (ex instanceof PersistentObjectException) { 668 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 669 } 670 if (ex instanceof TransientObjectException) { 671 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 672 } 673 if (ex instanceof ObjectDeletedException) { 674 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 675 } 676 if (ex instanceof UnresolvableObjectException) { 677 return new HibernateObjectRetrievalFailureException((UnresolvableObjectException) ex); 678 } 679 if (ex instanceof WrongClassException) { 680 return new HibernateObjectRetrievalFailureException((WrongClassException) ex); 681 } 682 if (ex instanceof StaleObjectStateException) { 683 return new HibernateOptimisticLockingFailureException((StaleObjectStateException) ex); 684 } 685 if (ex instanceof StaleStateException) { 686 return new HibernateOptimisticLockingFailureException((StaleStateException) ex); 687 } 688 689 // fallback 690 return new HibernateSystemException(ex); 691 } 692 693 694 /** 695 * Determine whether deferred close is active for the current thread 696 * and the given SessionFactory. 697 * @param sessionFactory the Hibernate SessionFactory to check 698 * @return whether deferred close is active 699 */ isDeferredCloseActive(SessionFactory sessionFactory)700 public static boolean isDeferredCloseActive(SessionFactory sessionFactory) { 701 Assert.notNull(sessionFactory, "No SessionFactory specified"); 702 Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get(); 703 return (holderMap != null && holderMap.containsKey(sessionFactory)); 704 } 705 706 /** 707 * Initialize deferred close for the current thread and the given SessionFactory. 708 * Sessions will not be actually closed on close calls then, but rather at a 709 * {@link #processDeferredClose} call at a finishing point (like request completion). 710 * <p>Used by {@link org.springframework.orm.hibernate3.support.OpenSessionInViewFilter} 711 * and {@link org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor} 712 * when not configured for a single session. 713 * @param sessionFactory the Hibernate SessionFactory to initialize deferred close for 714 * @see #processDeferredClose 715 * @see #releaseSession 716 * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter#setSingleSession 717 * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor#setSingleSession 718 */ initDeferredClose(SessionFactory sessionFactory)719 public static void initDeferredClose(SessionFactory sessionFactory) { 720 Assert.notNull(sessionFactory, "No SessionFactory specified"); 721 logger.debug("Initializing deferred close of Hibernate Sessions"); 722 Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get(); 723 if (holderMap == null) { 724 holderMap = new HashMap<SessionFactory, Set<Session>>(); 725 deferredCloseHolder.set(holderMap); 726 } 727 holderMap.put(sessionFactory, new LinkedHashSet<Session>(4)); 728 } 729 730 /** 731 * Process all Hibernate Sessions that have been registered for deferred close 732 * for the given SessionFactory. 733 * @param sessionFactory the Hibernate SessionFactory to process deferred close for 734 * @see #initDeferredClose 735 * @see #releaseSession 736 */ processDeferredClose(SessionFactory sessionFactory)737 public static void processDeferredClose(SessionFactory sessionFactory) { 738 Assert.notNull(sessionFactory, "No SessionFactory specified"); 739 Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get(); 740 if (holderMap == null || !holderMap.containsKey(sessionFactory)) { 741 throw new IllegalStateException("Deferred close not active for SessionFactory [" + sessionFactory + "]"); 742 } 743 logger.debug("Processing deferred close of Hibernate Sessions"); 744 Set<Session> sessions = holderMap.remove(sessionFactory); 745 for (Session session : sessions) { 746 closeSession(session); 747 } 748 if (holderMap.isEmpty()) { 749 deferredCloseHolder.remove(); 750 } 751 } 752 753 /** 754 * Close the given Session, created via the given factory, 755 * if it is not managed externally (i.e. not bound to the thread). 756 * @param session the Hibernate Session to close (may be <code>null</code>) 757 * @param sessionFactory Hibernate SessionFactory that the Session was created with 758 * (may be <code>null</code>) 759 */ releaseSession(Session session, SessionFactory sessionFactory)760 public static void releaseSession(Session session, SessionFactory sessionFactory) { 761 if (session == null) { 762 return; 763 } 764 // Only close non-transactional Sessions. 765 if (!isSessionTransactional(session, sessionFactory)) { 766 closeSessionOrRegisterDeferredClose(session, sessionFactory); 767 } 768 } 769 770 /** 771 * Close the given Session or register it for deferred close. 772 * @param session the Hibernate Session to close 773 * @param sessionFactory Hibernate SessionFactory that the Session was created with 774 * (may be <code>null</code>) 775 * @see #initDeferredClose 776 * @see #processDeferredClose 777 */ closeSessionOrRegisterDeferredClose(Session session, SessionFactory sessionFactory)778 static void closeSessionOrRegisterDeferredClose(Session session, SessionFactory sessionFactory) { 779 Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get(); 780 if (holderMap != null && sessionFactory != null && holderMap.containsKey(sessionFactory)) { 781 logger.debug("Registering Hibernate Session for deferred close"); 782 // Switch Session to FlushMode.MANUAL for remaining lifetime. 783 session.setFlushMode(FlushMode.MANUAL); 784 Set<Session> sessions = holderMap.get(sessionFactory); 785 sessions.add(session); 786 } 787 else { 788 closeSession(session); 789 } 790 } 791 792 /** 793 * Perform actual closing of the Hibernate Session, 794 * catching and logging any cleanup exceptions thrown. 795 * @param session the Hibernate Session to close (may be <code>null</code>) 796 * @see org.hibernate.Session#close() 797 */ closeSession(Session session)798 public static void closeSession(Session session) { 799 if (session != null) { 800 logger.debug("Closing Hibernate Session"); 801 try { 802 session.close(); 803 } 804 catch (HibernateException ex) { 805 logger.debug("Could not close Hibernate Session", ex); 806 } 807 catch (Throwable ex) { 808 logger.debug("Unexpected exception on closing Hibernate Session", ex); 809 } 810 } 811 } 812 813 } 814