001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.orm.hibernate3;
018:
019: import javax.sql.DataSource;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.hibernate.HibernateException;
024: import org.hibernate.JDBCException;
025: import org.hibernate.SessionFactory;
026:
027: import org.springframework.beans.factory.DisposableBean;
028: import org.springframework.beans.factory.FactoryBean;
029: import org.springframework.beans.factory.InitializingBean;
030: import org.springframework.dao.DataAccessException;
031: import org.springframework.dao.support.PersistenceExceptionTranslator;
032: import org.springframework.jdbc.support.SQLExceptionTranslator;
033:
034: /**
035: * Abstract {@link org.springframework.beans.factory.FactoryBean} that creates
036: * a Hibernate {@link org.hibernate.SessionFactory} within a Spring application
037: * context, providing general infrastructure not related to Hibernate's
038: * specific configuration API.
039: *
040: * <p>This class implements the
041: * {@link org.springframework.dao.support.PersistenceExceptionTranslator}
042: * interface, as autodetected by Spring's
043: * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor},
044: * for AOP-based translation of native exceptions to Spring DataAccessExceptions.
045: * Hence, the presence of e.g. LocalSessionFactoryBean automatically enables
046: * a PersistenceExceptionTranslationPostProcessor to translate Hibernate exceptions.
047: *
048: * <p>This class mainly serves as common base class for {@link LocalSessionFactoryBean}.
049: * For details on typical SessionFactory setup, see the LocalSessionFactoryBean javadoc.
050: *
051: * @author Juergen Hoeller
052: * @since 2.0
053: * @see #setExposeTransactionAwareSessionFactory
054: * @see org.hibernate.SessionFactory#getCurrentSession()
055: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
056: */
057: public abstract class AbstractSessionFactoryBean implements
058: FactoryBean, InitializingBean, DisposableBean,
059: PersistenceExceptionTranslator {
060:
061: /** Logger available to subclasses */
062: protected final Log logger = LogFactory.getLog(getClass());
063:
064: private DataSource dataSource;
065:
066: private boolean useTransactionAwareDataSource = false;
067:
068: private boolean exposeTransactionAwareSessionFactory = true;
069:
070: private SQLExceptionTranslator jdbcExceptionTranslator;
071:
072: private SessionFactory sessionFactory;
073:
074: /**
075: * Set the DataSource to be used by the SessionFactory.
076: * If set, this will override corresponding settings in Hibernate properties.
077: * <p>If this is set, the Hibernate settings should not define
078: * a connection provider to avoid meaningless double configuration.
079: * <p>If using HibernateTransactionManager as transaction strategy, consider
080: * proxying your target DataSource with a LazyConnectionDataSourceProxy.
081: * This defers fetching of an actual JDBC Connection until the first JDBC
082: * Statement gets executed, even within JDBC transactions (as performed by
083: * HibernateTransactionManager). Such lazy fetching is particularly beneficial
084: * for read-only operations, in particular if the chances of resolving the
085: * result in the second-level cache are high.
086: * <p>As JTA and transactional JNDI DataSources already provide lazy enlistment
087: * of JDBC Connections, LazyConnectionDataSourceProxy does not add value with
088: * JTA (i.e. Spring's JtaTransactionManager) as transaction strategy.
089: * @see #setUseTransactionAwareDataSource
090: * @see HibernateTransactionManager
091: * @see org.springframework.transaction.jta.JtaTransactionManager
092: * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy
093: */
094: public void setDataSource(DataSource dataSource) {
095: this .dataSource = dataSource;
096: }
097:
098: /**
099: * Return the DataSource to be used by the SessionFactory.
100: */
101: public DataSource getDataSource() {
102: return this .dataSource;
103: }
104:
105: /**
106: * Set whether to use a transaction-aware DataSource for the SessionFactory,
107: * i.e. whether to automatically wrap the passed-in DataSource with Spring's
108: * TransactionAwareDataSourceProxy.
109: * <p>Default is "false": LocalSessionFactoryBean is usually used with Spring's
110: * HibernateTransactionManager or JtaTransactionManager, both of which work nicely
111: * on a plain JDBC DataSource. Hibernate Sessions and their JDBC Connections are
112: * fully managed by the Hibernate/JTA transaction infrastructure in such a scenario.
113: * <p>If you switch this flag to "true", Spring's Hibernate access will be able to
114: * <i>participate in JDBC-based transactions managed outside of Hibernate</i>
115: * (for example, by Spring's DataSourceTransactionManager). This can be convenient
116: * if you need a different local transaction strategy for another O/R mapping tool,
117: * for example, but still want Hibernate access to join into those transactions.
118: * <p>A further benefit of this option is that <i>plain Sessions opened directly
119: * via the SessionFactory</i>, outside of Spring's Hibernate support, will still
120: * participate in active Spring-managed transactions. However, consider using
121: * Hibernate's <code>getCurrentSession()</code> method instead (see javadoc of
122: * "exposeTransactionAwareSessionFactory" property).
123: * <p><b>WARNING:</b> When using a transaction-aware JDBC DataSource in combination
124: * with OpenSessionInViewFilter/Interceptor, whether participating in JTA or
125: * external JDBC-based transactions, it is strongly recommended to set Hibernate's
126: * Connection release mode to "after_transaction" or "after_statement", which
127: * guarantees proper Connection handling in such a scenario. In contrast to that,
128: * HibernateTransactionManager generally requires release mode "on_close".
129: * <p>Note: If you want to use Hibernate's Connection release mode "after_statement"
130: * with a DataSource specified on this LocalSessionFactoryBean (for example, a
131: * JTA-aware DataSource fetched from JNDI), switch this setting to "true".
132: * Else, the ConnectionProvider used underneath will vote against aggressive
133: * release and thus silently switch to release mode "after_transaction".
134: * @see #setDataSource
135: * @see #setExposeTransactionAwareSessionFactory
136: * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
137: * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
138: * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
139: * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
140: * @see HibernateTransactionManager
141: * @see org.springframework.transaction.jta.JtaTransactionManager
142: */
143: public void setUseTransactionAwareDataSource(
144: boolean useTransactionAwareDataSource) {
145: this .useTransactionAwareDataSource = useTransactionAwareDataSource;
146: }
147:
148: /**
149: * Return whether to use a transaction-aware DataSource for the SessionFactory.
150: */
151: protected boolean isUseTransactionAwareDataSource() {
152: return this .useTransactionAwareDataSource;
153: }
154:
155: /**
156: * Set whether to expose a transaction-aware current Session from the
157: * SessionFactory's <code>getCurrentSession()</code> method, returning the
158: * Session that's associated with the current Spring-managed transaction, if any.
159: * <p>Default is "true", letting data access code work with the plain
160: * Hibernate SessionFactory and its <code>getCurrentSession()</code> method,
161: * while still being able to participate in current Spring-managed transactions:
162: * with any transaction management strategy, either local or JTA / EJB CMT,
163: * and any transaction synchronization mechanism, either Spring or JTA.
164: * Furthermore, <code>getCurrentSession()</code> will also seamlessly work with
165: * a request-scoped Session managed by OpenSessionInViewFilter/Interceptor.
166: * <p>Turn this flag off to expose the plain Hibernate SessionFactory with
167: * Hibernate's default <code>getCurrentSession()</code> behavior, supporting
168: * plain JTA synchronization only. Alternatively, simply override the
169: * corresponding Hibernate property "hibernate.current_session_context_class".
170: * @see SpringSessionContext
171: * @see org.hibernate.SessionFactory#getCurrentSession()
172: * @see org.springframework.transaction.jta.JtaTransactionManager
173: * @see HibernateTransactionManager
174: * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
175: * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
176: */
177: public void setExposeTransactionAwareSessionFactory(
178: boolean exposeTransactionAwareSessionFactory) {
179: this .exposeTransactionAwareSessionFactory = exposeTransactionAwareSessionFactory;
180: }
181:
182: /**
183: * Return whether to expose a transaction-aware proxy for the SessionFactory.
184: */
185: protected boolean isExposeTransactionAwareSessionFactory() {
186: return this .exposeTransactionAwareSessionFactory;
187: }
188:
189: /**
190: * Set the JDBC exception translator for the SessionFactory,
191: * exposed via the PersistenceExceptionTranslator interface.
192: * <p>Applied to any SQLException root cause of a Hibernate JDBCException,
193: * overriding Hibernate's default SQLException translation (which is
194: * based on Hibernate's Dialect for a specific target database).
195: * @param jdbcExceptionTranslator the exception translator
196: * @see java.sql.SQLException
197: * @see org.hibernate.JDBCException
198: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
199: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
200: * @see org.springframework.dao.support.PersistenceExceptionTranslator
201: */
202: public void setJdbcExceptionTranslator(
203: SQLExceptionTranslator jdbcExceptionTranslator) {
204: this .jdbcExceptionTranslator = jdbcExceptionTranslator;
205: }
206:
207: /**
208: * Build and expose the SessionFactory.
209: * @see #buildSessionFactory()
210: * @see #wrapSessionFactoryIfNecessary
211: */
212: public void afterPropertiesSet() throws Exception {
213: SessionFactory rawSf = buildSessionFactory();
214: this .sessionFactory = wrapSessionFactoryIfNecessary(rawSf);
215: afterSessionFactoryCreation();
216: }
217:
218: /**
219: * Wrap the given SessionFactory with a proxy, if demanded.
220: * <p>The default implementation simply returns the given SessionFactory as-is.
221: * Subclasses may override this to implement transaction awareness through
222: * a SessionFactory proxy, for example.
223: * @param rawSf the raw SessionFactory as built by {@link #buildSessionFactory()}
224: * @return the SessionFactory reference to expose
225: * @see #buildSessionFactory()
226: */
227: protected SessionFactory wrapSessionFactoryIfNecessary(
228: SessionFactory rawSf) {
229: return rawSf;
230: }
231:
232: /**
233: * Return the exposed SessionFactory.
234: * Will throw an exception if not initialized yet.
235: * @return the SessionFactory (never <code>null</code>)
236: * @throws IllegalStateException if the SessionFactory has not been initialized yet
237: */
238: protected final SessionFactory getSessionFactory() {
239: if (this .sessionFactory == null) {
240: throw new IllegalStateException(
241: "SessionFactory not initialized yet");
242: }
243: return this .sessionFactory;
244: }
245:
246: /**
247: * Close the SessionFactory on bean factory shutdown.
248: */
249: public void destroy() throws HibernateException {
250: logger.info("Closing Hibernate SessionFactory");
251: try {
252: beforeSessionFactoryDestruction();
253: } finally {
254: this .sessionFactory.close();
255: }
256: }
257:
258: /**
259: * Return the singleton SessionFactory.
260: */
261: public Object getObject() {
262: return this .sessionFactory;
263: }
264:
265: public Class getObjectType() {
266: return (this .sessionFactory != null) ? this .sessionFactory
267: .getClass() : SessionFactory.class;
268: }
269:
270: public boolean isSingleton() {
271: return true;
272: }
273:
274: /**
275: * Implementation of the PersistenceExceptionTranslator interface,
276: * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
277: * <p>Converts the exception if it is a HibernateException;
278: * else returns <code>null</code> to indicate an unknown exception.
279: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
280: * @see #convertHibernateAccessException
281: */
282: public DataAccessException translateExceptionIfPossible(
283: RuntimeException ex) {
284: if (ex instanceof HibernateException) {
285: return convertHibernateAccessException((HibernateException) ex);
286: }
287: return null;
288: }
289:
290: /**
291: * Convert the given HibernateException to an appropriate exception from the
292: * <code>org.springframework.dao</code> hierarchy.
293: * <p>Will automatically apply a specified SQLExceptionTranslator to a
294: * Hibernate JDBCException, else rely on Hibernate's default translation.
295: * @param ex HibernateException that occured
296: * @return a corresponding DataAccessException
297: * @see SessionFactoryUtils#convertHibernateAccessException
298: * @see #setJdbcExceptionTranslator
299: */
300: protected DataAccessException convertHibernateAccessException(
301: HibernateException ex) {
302: if (this .jdbcExceptionTranslator != null
303: && ex instanceof JDBCException) {
304: JDBCException jdbcEx = (JDBCException) ex;
305: return this .jdbcExceptionTranslator.translate(
306: "Hibernate operation: " + jdbcEx.getMessage(),
307: jdbcEx.getSQL(), jdbcEx.getSQLException());
308: }
309: return SessionFactoryUtils.convertHibernateAccessException(ex);
310: }
311:
312: /**
313: * Build the underlying Hibernate SessionFactory.
314: * @return the raw SessionFactory (potentially to be wrapped with a
315: * transaction-aware proxy before it is exposed to the application)
316: * @throws Exception in case of initialization failure
317: */
318: protected abstract SessionFactory buildSessionFactory()
319: throws Exception;
320:
321: /**
322: * Hook that allows post-processing after the SessionFactory has been
323: * successfully created. The SessionFactory is already available through
324: * <code>getSessionFactory()</code> at this point.
325: * <p>This implementation is empty.
326: * @throws Exception in case of initialization failure
327: * @see #getSessionFactory()
328: */
329: protected void afterSessionFactoryCreation() throws Exception {
330: }
331:
332: /**
333: * Hook that allows shutdown processing before the SessionFactory
334: * will be closed. The SessionFactory is still available through
335: * <code>getSessionFactory()</code> at this point.
336: * <p>This implementation is empty.
337: * @see #getSessionFactory()
338: */
339: protected void beforeSessionFactoryDestruction() {
340: }
341:
342: }
|