/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.orm.hibernate3; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.SessionFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.jdbc.support.SQLExceptionTranslator; /** * Abstract {@link org.springframework.beans.factory.FactoryBean} that creates * a Hibernate {@link org.hibernate.SessionFactory} within a Spring application * context, providing general infrastructure not related to Hibernate's * specific configuration API. * * <p>This class implements the * {@link org.springframework.dao.support.PersistenceExceptionTranslator} * interface, as autodetected by Spring's * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}, * for AOP-based translation of native exceptions to Spring DataAccessExceptions. * Hence, the presence of e.g. LocalSessionFactoryBean automatically enables * a PersistenceExceptionTranslationPostProcessor to translate Hibernate exceptions. * * <p>This class mainly serves as common base class for {@link LocalSessionFactoryBean}. * For details on typical SessionFactory setup, see the LocalSessionFactoryBean javadoc. * * @author Juergen Hoeller * @since 2.0 * @see #setExposeTransactionAwareSessionFactory * @see org.hibernate.SessionFactory#getCurrentSession() * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor */ public abstract class AbstractSessionFactoryBean implements FactoryBean, InitializingBean, DisposableBean, PersistenceExceptionTranslator { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); private DataSource dataSource; private boolean useTransactionAwareDataSource = false; private boolean exposeTransactionAwareSessionFactory = true; private SQLExceptionTranslator jdbcExceptionTranslator; private SessionFactory sessionFactory; /** * Set the DataSource to be used by the SessionFactory. * If set, this will override corresponding settings in Hibernate properties. * <p>If this is set, the Hibernate settings should not define * a connection provider to avoid meaningless double configuration. * <p>If using HibernateTransactionManager as transaction strategy, consider * proxying your target DataSource with a LazyConnectionDataSourceProxy. * This defers fetching of an actual JDBC Connection until the first JDBC * Statement gets executed, even within JDBC transactions (as performed by * HibernateTransactionManager). Such lazy fetching is particularly beneficial * for read-only operations, in particular if the chances of resolving the * result in the second-level cache are high. * <p>As JTA and transactional JNDI DataSources already provide lazy enlistment * of JDBC Connections, LazyConnectionDataSourceProxy does not add value with * JTA (i.e. Spring's JtaTransactionManager) as transaction strategy. * @see #setUseTransactionAwareDataSource * @see HibernateTransactionManager * @see org.springframework.transaction.jta.JtaTransactionManager * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Return the DataSource to be used by the SessionFactory. */ public DataSource getDataSource() { return this.dataSource; } /** * Set whether to use a transaction-aware DataSource for the SessionFactory, * i.e. whether to automatically wrap the passed-in DataSource with Spring's * TransactionAwareDataSourceProxy. * <p>Default is "false": LocalSessionFactoryBean is usually used with Spring's * HibernateTransactionManager or JtaTransactionManager, both of which work nicely * on a plain JDBC DataSource. Hibernate Sessions and their JDBC Connections are * fully managed by the Hibernate/JTA transaction infrastructure in such a scenario. * <p>If you switch this flag to "true", Spring's Hibernate access will be able to * <i>participate in JDBC-based transactions managed outside of Hibernate</i> * (for example, by Spring's DataSourceTransactionManager). This can be convenient * if you need a different local transaction strategy for another O/R mapping tool, * for example, but still want Hibernate access to join into those transactions. * <p>A further benefit of this option is that <i>plain Sessions opened directly * via the SessionFactory</i>, outside of Spring's Hibernate support, will still * participate in active Spring-managed transactions. However, consider using * Hibernate's <code>getCurrentSession()</code> method instead (see javadoc of * "exposeTransactionAwareSessionFactory" property). * <p><b>WARNING:</b> When using a transaction-aware JDBC DataSource in combination * with OpenSessionInViewFilter/Interceptor, whether participating in JTA or * external JDBC-based transactions, it is strongly recommended to set Hibernate's * Connection release mode to "after_transaction" or "after_statement", which * guarantees proper Connection handling in such a scenario. In contrast to that, * HibernateTransactionManager generally requires release mode "on_close". * <p>Note: If you want to use Hibernate's Connection release mode "after_statement" * with a DataSource specified on this LocalSessionFactoryBean (for example, a * JTA-aware DataSource fetched from JNDI), switch this setting to "true". * Otherwise, the ConnectionProvider used underneath will vote against aggressive * release and thus silently switch to release mode "after_transaction". * @see #setDataSource * @see #setExposeTransactionAwareSessionFactory * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy * @see org.springframework.jdbc.datasource.DataSourceTransactionManager * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor * @see HibernateTransactionManager * @see org.springframework.transaction.jta.JtaTransactionManager */ public void setUseTransactionAwareDataSource(boolean useTransactionAwareDataSource) { this.useTransactionAwareDataSource = useTransactionAwareDataSource; } /** * Return whether to use a transaction-aware DataSource for the SessionFactory. */ protected boolean isUseTransactionAwareDataSource() { return this.useTransactionAwareDataSource; } /** * Set whether to expose a transaction-aware current Session from the * SessionFactory's <code>getCurrentSession()</code> method, returning the * Session that's associated with the current Spring-managed transaction, if any. * <p>Default is "true", letting data access code work with the plain * Hibernate SessionFactory and its <code>getCurrentSession()</code> method, * while still being able to participate in current Spring-managed transactions: * with any transaction management strategy, either local or JTA / EJB CMT, * and any transaction synchronization mechanism, either Spring or JTA. * Furthermore, <code>getCurrentSession()</code> will also seamlessly work with * a request-scoped Session managed by OpenSessionInViewFilter/Interceptor. * <p>Turn this flag off to expose the plain Hibernate SessionFactory with * Hibernate's default <code>getCurrentSession()</code> behavior, supporting * plain JTA synchronization only. Alternatively, simply override the * corresponding Hibernate property "hibernate.current_session_context_class". * @see SpringSessionContext * @see org.hibernate.SessionFactory#getCurrentSession() * @see org.springframework.transaction.jta.JtaTransactionManager * @see HibernateTransactionManager * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor */ public void setExposeTransactionAwareSessionFactory(boolean exposeTransactionAwareSessionFactory) { this.exposeTransactionAwareSessionFactory = exposeTransactionAwareSessionFactory; } /** * Return whether to expose a transaction-aware proxy for the SessionFactory. */ protected boolean isExposeTransactionAwareSessionFactory() { return this.exposeTransactionAwareSessionFactory; } /** * Set the JDBC exception translator for the SessionFactory, * exposed via the PersistenceExceptionTranslator interface. * <p>Applied to any SQLException root cause of a Hibernate JDBCException, * overriding Hibernate's default SQLException translation (which is * based on Hibernate's Dialect for a specific target database). * @param jdbcExceptionTranslator the exception translator * @see java.sql.SQLException * @see org.hibernate.JDBCException * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator * @see org.springframework.dao.support.PersistenceExceptionTranslator */ public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { this.jdbcExceptionTranslator = jdbcExceptionTranslator; } /** * Build and expose the SessionFactory. * @see #buildSessionFactory() * @see #wrapSessionFactoryIfNecessary */ public void afterPropertiesSet() throws Exception { SessionFactory rawSf = buildSessionFactory(); this.sessionFactory = wrapSessionFactoryIfNecessary(rawSf); afterSessionFactoryCreation(); } /** * Wrap the given SessionFactory with a proxy, if demanded. * <p>The default implementation simply returns the given SessionFactory as-is. * Subclasses may override this to implement transaction awareness through * a SessionFactory proxy, for example. * @param rawSf the raw SessionFactory as built by {@link #buildSessionFactory()} * @return the SessionFactory reference to expose * @see #buildSessionFactory() */ protected SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) { return rawSf; } /** * Return the exposed SessionFactory. * Will throw an exception if not initialized yet. * @return the SessionFactory (never <code>null</code>) * @throws IllegalStateException if the SessionFactory has not been initialized yet */ protected final SessionFactory getSessionFactory() { if (this.sessionFactory == null) { throw new IllegalStateException("SessionFactory not initialized yet"); } return this.sessionFactory; } /** * Close the SessionFactory on bean factory shutdown. */ public void destroy() throws HibernateException { logger.info("Closing Hibernate SessionFactory"); try { beforeSessionFactoryDestruction(); } finally { this.sessionFactory.close(); } } /** * Return the singleton SessionFactory. */ public Object getObject() { return this.sessionFactory; } public Class getObjectType() { return (this.sessionFactory != null) ? this.sessionFactory.getClass() : SessionFactory.class; } public boolean isSingleton() { return true; } /** * Implementation of the PersistenceExceptionTranslator interface, * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor. * <p>Converts the exception if it is a HibernateException; * else returns <code>null</code> to indicate an unknown exception. * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor * @see #convertHibernateAccessException */ public DataAccessException translateExceptionIfPossible(RuntimeException ex) { if (ex instanceof HibernateException) { return convertHibernateAccessException((HibernateException) ex); } return null; } /** * Convert the given HibernateException to an appropriate exception from the * <code>org.springframework.dao</code> hierarchy. * <p>Will automatically apply a specified SQLExceptionTranslator to a * Hibernate JDBCException, else rely on Hibernate's default translation. * @param ex HibernateException that occured * @return a corresponding DataAccessException * @see SessionFactoryUtils#convertHibernateAccessException * @see #setJdbcExceptionTranslator */ protected DataAccessException convertHibernateAccessException(HibernateException ex) { if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) { JDBCException jdbcEx = (JDBCException) ex; return this.jdbcExceptionTranslator.translate( "Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException()); } return SessionFactoryUtils.convertHibernateAccessException(ex); } /** * Build the underlying Hibernate SessionFactory. * @return the raw SessionFactory (potentially to be wrapped with a * transaction-aware proxy before it is exposed to the application) * @throws Exception in case of initialization failure */ protected abstract SessionFactory buildSessionFactory() throws Exception; /** * Hook that allows post-processing after the SessionFactory has been * successfully created. The SessionFactory is already available through * <code>getSessionFactory()</code> at this point. * <p>This implementation is empty. * @throws Exception in case of initialization failure * @see #getSessionFactory() */ protected void afterSessionFactoryCreation() throws Exception { } /** * Hook that allows shutdown processing before the SessionFactory * will be closed. The SessionFactory is still available through * <code>getSessionFactory()</code> at this point. * <p>This implementation is empty. * @see #getSessionFactory() */ protected void beforeSessionFactoryDestruction() { } }