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