From 0ff83606dff11b61401145a2f569537854e51fb1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 11 Oct 2011 00:53:01 +0000 Subject: [PATCH] committed initial Hibernate 4.0 support --- .../HibernateExceptionTranslator.java | 53 ++ .../hibernate4/HibernateJdbcException.java | 54 ++ ...ernateObjectRetrievalFailureException.java | 42 ++ ...nateOptimisticLockingFailureException.java | 42 ++ .../hibernate4/HibernateQueryException.java | 44 ++ .../hibernate4/HibernateSystemException.java | 43 ++ .../HibernateTransactionManager.java | 696 ++++++++++++++++++ .../hibernate4/LocalSessionFactoryBean.java | 229 ++++++ .../orm/hibernate4/SessionFactoryUtils.java | 181 +++++ .../orm/hibernate4/SessionHolder.java | 81 ++ .../SpringFlushSynchronization.java | 64 ++ .../hibernate4/SpringJtaSessionContext.java | 48 ++ .../orm/hibernate4/SpringSessionContext.java | 101 +++ .../SpringSessionSynchronization.java | 122 +++ .../orm/hibernate4/package-info.java | 17 + .../support/OpenSessionInViewFilter.java | 180 +++++ .../support/OpenSessionInViewInterceptor.java | 176 +++++ .../orm/hibernate4/support/package-info.java | 24 + 18 files changed, 2197 insertions(+) create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateExceptionTranslator.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateJdbcException.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateObjectRetrievalFailureException.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateOptimisticLockingFailureException.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateQueryException.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateSystemException.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionHolder.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringFlushSynchronization.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringJtaSessionContext.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionSynchronization.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/package-info.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java create mode 100644 org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/package-info.java diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateExceptionTranslator.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateExceptionTranslator.java new file mode 100644 index 00000000000..8b025793606 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateExceptionTranslator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.HibernateException; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; + +/** + * {@link PersistenceExceptionTranslator} capable of translating {@link HibernateException} + * instances to Spring's {@link org.springframework.dao.DataAccessException} hierarchy. + * + * @author Juergen Hoeller + * @since 3.1 + * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor + * @see SessionFactoryUtils#convertHibernateAccessException(HibernateException) + */ +public class HibernateExceptionTranslator implements PersistenceExceptionTranslator { + + 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} hierarchy. + * @param ex HibernateException that occured + * @return a corresponding DataAccessException + * @see SessionFactoryUtils#convertHibernateAccessException + */ + protected DataAccessException convertHibernateAccessException(HibernateException ex) { + return SessionFactoryUtils.convertHibernateAccessException(ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateJdbcException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateJdbcException.java new file mode 100644 index 00000000000..238e5b532d6 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateJdbcException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import java.sql.SQLException; + +import org.hibernate.JDBCException; + +import org.springframework.dao.UncategorizedDataAccessException; + +/** + * Hibernate-specific subclass of UncategorizedDataAccessException, + * for JDBC exceptions that Hibernate wrapped. + * + * @author Juergen Hoeller + * @since 3.1 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateJdbcException extends UncategorizedDataAccessException { + + public HibernateJdbcException(JDBCException ex) { + super("JDBC exception on Hibernate data access: SQLException for SQL [" + ex.getSQL() + "]; SQL state [" + + ex.getSQLState() + "]; error code [" + ex.getErrorCode() + "]; " + ex.getMessage(), ex); + } + + /** + * Return the underlying SQLException. + */ + public SQLException getSQLException() { + return ((JDBCException) getCause()).getSQLException(); + } + + /** + * Return the SQL that led to the problem. + */ + public String getSql() { + return ((JDBCException) getCause()).getSQL(); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateObjectRetrievalFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateObjectRetrievalFailureException.java new file mode 100644 index 00000000000..5275e39fe18 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateObjectRetrievalFailureException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.UnresolvableObjectException; +import org.hibernate.WrongClassException; + +import org.springframework.orm.ObjectRetrievalFailureException; + +/** + * Hibernate-specific subclass of ObjectRetrievalFailureException. + * Converts Hibernate's UnresolvableObjectException and WrongClassException. + * + * @author Juergen Hoeller + * @since 3.1 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException { + + public HibernateObjectRetrievalFailureException(UnresolvableObjectException ex) { + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); + } + + public HibernateObjectRetrievalFailureException(WrongClassException ex) { + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateOptimisticLockingFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateOptimisticLockingFailureException.java new file mode 100644 index 00000000000..c77abd5f83f --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateOptimisticLockingFailureException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.StaleObjectStateException; +import org.hibernate.StaleStateException; + +import org.springframework.orm.ObjectOptimisticLockingFailureException; + +/** + * Hibernate-specific subclass of ObjectOptimisticLockingFailureException. + * Converts Hibernate's StaleObjectStateException and StaleStateException. + * + * @author Juergen Hoeller + * @since 3.1 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException { + + public HibernateOptimisticLockingFailureException(StaleObjectStateException ex) { + super(ex.getEntityName(), ex.getIdentifier(), ex); + } + + public HibernateOptimisticLockingFailureException(StaleStateException ex) { + super(ex.getMessage(), ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateQueryException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateQueryException.java new file mode 100644 index 00000000000..1a389368240 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateQueryException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.QueryException; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Hibernate-specific subclass of InvalidDataAccessResourceUsageException, + * thrown on invalid HQL query syntax. + * + * @author Juergen Hoeller + * @since 3.1 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateQueryException extends InvalidDataAccessResourceUsageException { + + public HibernateQueryException(QueryException ex) { + super(ex.getMessage(), ex); + } + + /** + * Return the HQL query string that was invalid. + */ + public String getQueryString() { + return ((QueryException) getCause()).getQueryString(); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateSystemException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateSystemException.java new file mode 100644 index 00000000000..1dd1fdd51c5 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateSystemException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.HibernateException; + +import org.springframework.dao.UncategorizedDataAccessException; + +/** + * Hibernate-specific subclass of UncategorizedDataAccessException, + * for Hibernate system errors that do not match any concrete + * org.springframework.dao exceptions. + * + * @author Juergen Hoeller + * @since 3.1 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateSystemException extends UncategorizedDataAccessException { + + /** + * Create a new HibernateSystemException, + * wrapping an arbitrary HibernateException. + * @param cause the HibernateException thrown + */ + public HibernateSystemException(HibernateException cause) { + super(cause != null ? cause.getMessage() : null, cause); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java new file mode 100644 index 00000000000..18823000cce --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java @@ -0,0 +1,696 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import java.sql.Connection; +import javax.sql.DataSource; + +import org.hibernate.ConnectionReleaseMode; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.transaction.spi.TransactionContext; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.jdbc.datasource.ConnectionHolder; +import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport; +import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.InvalidIsolationLevelException; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.ResourceTransactionManager; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * {@link org.springframework.transaction.PlatformTransactionManager} + * implementation for a single Hibernate {@link org.hibernate.SessionFactory}. + * Binds a Hibernate Session from the specified factory to the thread, + * potentially allowing for one thread-bound Session per factory. + * SessionFactory.getCurrentSession() is required for Hibernate + * access code that needs to support this transaction handling mechanism, + * with the SessionFactory being configured with {@link SpringSessionContext}. + * + *

Supports custom isolation levels, and timeouts that get applied as + * Hibernate transaction timeouts. + * + *

This transaction manager is appropriate for applications that use a single + * Hibernate SessionFactory for transactional data access, but it also supports + * direct DataSource access within a transaction (i.e. plain JDBC code working + * with the same DataSource). This allows for mixing services which access Hibernate + * and services which use plain JDBC (without being aware of Hibernate)! + * Application code needs to stick to the same simple Connection lookup pattern as + * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection} + * or going through a + * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}). + * + *

Note: To be able to register a DataSource's Connection for plain JDBC code, + * this instance needs to be aware of the DataSource ({@link #setDataSource}). + * The given DataSource should obviously match the one used by the given SessionFactory. + * + *

JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager}) + * is necessary for accessing multiple transactional resources within the same + * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in + * such a scenario (see container setup). + * + *

On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0 + * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} + * flag defaults to "false", though, as nested transactions will just apply to the + * JDBC Connection, not to the Hibernate Session and its cached objects. You can + * manually set the flag to "true" if you want to use nested transactions for + * JDBC access code which participates in Hibernate transactions (provided that + * your JDBC driver supports Savepoints). Note that Hibernate itself does not + * support nested transactions! Hence, do not expect Hibernate access code to + * semantically participate in a nested transaction. + * + * @author Juergen Hoeller + * @since 3.1 + * @see #setSessionFactory + * @see #setDataSource + * @see org.hibernate.SessionFactory#getCurrentSession() + * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection + * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection + * @see org.springframework.jdbc.core.JdbcTemplate + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + */ +@SuppressWarnings("serial") +public class HibernateTransactionManager extends AbstractPlatformTransactionManager + implements ResourceTransactionManager, InitializingBean { + + private SessionFactory sessionFactory; + + private DataSource dataSource; + + private boolean autodetectDataSource = true; + + private boolean prepareConnection = true; + + private boolean hibernateManagedSession = false; + + + /** + * Create a new HibernateTransactionManager instance. + * A SessionFactory has to be set to be able to use it. + * @see #setSessionFactory + */ + public HibernateTransactionManager() { + } + + /** + * Create a new HibernateTransactionManager instance. + * @param sessionFactory SessionFactory to manage transactions for + */ + public HibernateTransactionManager(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + afterPropertiesSet(); + } + + + /** + * Set the SessionFactory that this instance should manage transactions for. + */ + public void setSessionFactory(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + /** + * Return the SessionFactory that this instance should manage transactions for. + */ + public SessionFactory getSessionFactory() { + return this.sessionFactory; + } + + /** + * Set the JDBC DataSource that this instance should manage transactions for. + * The DataSource should match the one used by the Hibernate SessionFactory: + * for example, you could specify the same JNDI DataSource for both. + *

If the SessionFactory was configured with LocalDataSourceConnectionProvider, + * i.e. by Spring's SessionFactoryBuilder with a specified "dataSource", + * the DataSource will be auto-detected: You can still explictly specify the + * DataSource, but you don't need to in this case. + *

A transactional JDBC Connection for this DataSource will be provided to + * application code accessing this DataSource directly via DataSourceUtils + * or JdbcTemplate. The Connection will be taken from the Hibernate Session. + *

The DataSource specified here should be the target DataSource to manage + * transactions for, not a TransactionAwareDataSourceProxy. Only data access + * code may work with TransactionAwareDataSourceProxy, while the transaction + * manager needs to work on the underlying target DataSource. If there's + * nevertheless a TransactionAwareDataSourceProxy passed in, it will be + * unwrapped to extract its target DataSource. + * @see #setAutodetectDataSource + * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy + * @see org.springframework.jdbc.datasource.DataSourceUtils + * @see org.springframework.jdbc.core.JdbcTemplate + */ + public void setDataSource(DataSource dataSource) { + if (dataSource instanceof TransactionAwareDataSourceProxy) { + // If we got a TransactionAwareDataSourceProxy, we need to perform transactions + // for its underlying target DataSource, else data access code won't see + // properly exposed transactions (i.e. transactions for the target DataSource). + this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); + } + else { + this.dataSource = dataSource; + } + } + + /** + * Return the JDBC DataSource that this instance manages transactions for. + */ + public DataSource getDataSource() { + return this.dataSource; + } + + /** + * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory, + * if set via SessionFactoryBuilder's setDataSource. Default is "true". + *

Can be turned off to deliberately ignore an available DataSource, in order + * to not expose Hibernate transactions as JDBC transactions for that DataSource. + * @see #setDataSource + */ + public void setAutodetectDataSource(boolean autodetectDataSource) { + this.autodetectDataSource = autodetectDataSource; + } + + /** + * Set whether to prepare the underlying JDBC Connection of a transactional + * Hibernate Session, that is, whether to apply a transaction-specific + * isolation level and/or the transaction's read-only flag to the underlying + * JDBC Connection. + *

Default is "true". If you turn this flag off, the transaction manager + * will not support per-transaction isolation levels anymore. It will not + * call Connection.setReadOnly(true) for read-only transactions + * anymore either. If this flag is turned off, no cleanup of a JDBC Connection + * is required after a transaction, since no Connection settings will get modified. + * @see java.sql.Connection#setTransactionIsolation + * @see java.sql.Connection#setReadOnly + */ + public void setPrepareConnection(boolean prepareConnection) { + this.prepareConnection = prepareConnection; + } + + /** + * Set whether to operate on a Hibernate-managed Session instead of a + * Spring-managed Session, that is, whether to obtain the Session through + * Hibernate's {@link org.hibernate.SessionFactory#getCurrentSession()} + * instead of {@link org.hibernate.SessionFactory#openSession()} (with a Spring + * {@link org.springframework.transaction.support.TransactionSynchronizationManager} + * check preceding it). + *

Default is "false", i.e. using a Spring-managed Session: taking the current + * thread-bound Session if available (e.g. in an Open-Session-in-View scenario), + * creating a new Session for the current transaction otherwise. + *

Switch this flag to "true" in order to enforce use of a Hibernate-managed Session. + * Note that this requires {@link org.hibernate.SessionFactory#getCurrentSession()} + * to always return a proper Session when called for a Spring-managed transaction; + * transaction begin will fail if the getCurrentSession() call fails. + *

This mode will typically be used in combination with a custom Hibernate + * {@link org.hibernate.context.CurrentSessionContext} implementation that stores + * Sessions in a place other than Spring's TransactionSynchronizationManager. + * It may also be used in combination with Spring's Open-Session-in-View support + * (using Spring's default {@link SpringSessionContext}), in which case it subtly + * differs from the Spring-managed Session mode: The pre-bound Session will not + * receive a clear() call (on rollback) or a disconnect() + * call (on transaction completion) in such a scenario; this is rather left up + * to a custom CurrentSessionContext implementation (if desired). + */ + public void setHibernateManagedSession(boolean hibernateManagedSession) { + this.hibernateManagedSession = hibernateManagedSession; + } + + public void afterPropertiesSet() { + if (getSessionFactory() == null) { + throw new IllegalArgumentException("Property 'sessionFactory' is required"); + } + + // Check for SessionFactory's DataSource. + if (this.autodetectDataSource && getDataSource() == null) { + DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory()); + if (sfds != null) { + // Use the SessionFactory's DataSource for exposing transactions to JDBC code. + if (logger.isInfoEnabled()) { + logger.info("Using DataSource [" + sfds + + "] of Hibernate SessionFactory for HibernateTransactionManager"); + } + setDataSource(sfds); + } + } + } + + + public Object getResourceFactory() { + return getSessionFactory(); + } + + @Override + protected Object doGetTransaction() { + HibernateTransactionObject txObject = new HibernateTransactionObject(); + txObject.setSavepointAllowed(isNestedTransactionAllowed()); + + SessionHolder sessionHolder = + (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory()); + if (sessionHolder != null) { + if (logger.isDebugEnabled()) { + logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction"); + } + txObject.setSessionHolder(sessionHolder); + } + else if (this.hibernateManagedSession) { + try { + Session session = getSessionFactory().getCurrentSession(); + if (logger.isDebugEnabled()) { + logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction"); + } + txObject.setExistingSession(session); + } + catch (HibernateException ex) { + throw new DataAccessResourceFailureException( + "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex); + } + } + + if (getDataSource() != null) { + ConnectionHolder conHolder = (ConnectionHolder) + TransactionSynchronizationManager.getResource(getDataSource()); + txObject.setConnectionHolder(conHolder); + } + + return txObject; + } + + @Override + protected boolean isExistingTransaction(Object transaction) { + HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; + return (txObject.hasSpringManagedTransaction() || + (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction())); + } + + @Override + protected void doBegin(Object transaction, TransactionDefinition definition) { + HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; + + if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) { + throw new IllegalTransactionStateException( + "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " + + "running within DataSourceTransactionManager if told to manage the DataSource itself. " + + "It is recommended to use a single HibernateTransactionManager for all transactions " + + "on a single DataSource, no matter whether Hibernate or JDBC access."); + } + + Session session = null; + + try { + if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) { + Session newSession = getSessionFactory().openSession(); + if (logger.isDebugEnabled()) { + logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction"); + } + txObject.setSession(newSession); + } + + session = txObject.getSessionHolder().getSession(); + + if (this.prepareConnection && isSameConnectionForEntireSession(session)) { + // We're allowed to change the transaction settings of the JDBC Connection. + if (logger.isDebugEnabled()) { + logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]"); + } + Connection con = ((SessionImplementor) session).connection(); + Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); + txObject.setPreviousIsolationLevel(previousIsolationLevel); + } + else { + // Not allowed to change the transaction settings of the JDBC Connection. + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + // We should set a specific isolation level but are not allowed to... + throw new InvalidIsolationLevelException( + "HibernateTransactionManager is not allowed to support custom isolation levels: " + + "make sure that its 'prepareConnection' flag is on (the default) and that the " + + "Hibernate connection release mode is set to 'on_close' (SpringTransactionFactory's default)."); + } + if (logger.isDebugEnabled()) { + logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]"); + } + } + + if (definition.isReadOnly() && txObject.isNewSession()) { + // Just set to NEVER in case of a new Session for this transaction. + session.setFlushMode(FlushMode.MANUAL); + } + + if (!definition.isReadOnly() && !txObject.isNewSession()) { + // We need AUTO or COMMIT for a non-read-only transaction. + FlushMode flushMode = session.getFlushMode(); + if (FlushMode.isManualFlushMode(session.getFlushMode())) { + session.setFlushMode(FlushMode.AUTO); + txObject.getSessionHolder().setPreviousFlushMode(flushMode); + } + } + + Transaction hibTx; + + // Register transaction timeout. + int timeout = determineTimeout(definition); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+ + // Applies to all statements, also to inserts, updates and deletes! + hibTx = session.getTransaction(); + hibTx.setTimeout(timeout); + hibTx.begin(); + } + else { + // Open a plain Hibernate transaction without specified timeout. + hibTx = session.beginTransaction(); + } + + // Add the Hibernate transaction to the session holder. + txObject.getSessionHolder().setTransaction(hibTx); + + // Register the Hibernate Session's JDBC Connection for the DataSource, if set. + if (getDataSource() != null) { + Connection con = ((SessionImplementor) session).connection(); + ConnectionHolder conHolder = new ConnectionHolder(con); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + conHolder.setTimeoutInSeconds(timeout); + } + if (logger.isDebugEnabled()) { + logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]"); + } + TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); + txObject.setConnectionHolder(conHolder); + } + + // Bind the session holder to the thread. + if (txObject.isNewSessionHolder()) { + TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder()); + } + txObject.getSessionHolder().setSynchronizedWithTransaction(true); + } + + catch (Exception ex) { + if (txObject.isNewSession()) { + try { + if (session.getTransaction().isActive()) { + session.getTransaction().rollback(); + } + } + catch (Throwable ex2) { + logger.debug("Could not rollback Session after failed transaction begin", ex); + } + finally { + SessionFactoryUtils.closeSession(session); + } + } + throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex); + } + } + + @Override + protected Object doSuspend(Object transaction) { + HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; + txObject.setSessionHolder(null); + SessionHolder sessionHolder = + (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory()); + txObject.setConnectionHolder(null); + ConnectionHolder connectionHolder = null; + if (getDataSource() != null) { + connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource()); + } + return new SuspendedResourcesHolder(sessionHolder, connectionHolder); + } + + @Override + protected void doResume(Object transaction, Object suspendedResources) { + SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources; + if (TransactionSynchronizationManager.hasResource(getSessionFactory())) { + // From non-transactional code running in active transaction synchronization + // -> can be safely removed, will be closed on transaction completion. + TransactionSynchronizationManager.unbindResource(getSessionFactory()); + } + TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder()); + if (getDataSource() != null) { + TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder()); + } + } + + @Override + protected void doCommit(DefaultTransactionStatus status) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Committing Hibernate transaction on Session [" + + txObject.getSessionHolder().getSession() + "]"); + } + try { + txObject.getSessionHolder().getTransaction().commit(); + } + catch (org.hibernate.TransactionException ex) { + // assumably from commit call to the underlying JDBC connection + throw new TransactionSystemException("Could not commit Hibernate transaction", ex); + } + catch (HibernateException ex) { + // assumably failed to flush changes to database + throw convertHibernateAccessException(ex); + } + } + + @Override + protected void doRollback(DefaultTransactionStatus status) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Rolling back Hibernate transaction on Session [" + + txObject.getSessionHolder().getSession() + "]"); + } + try { + txObject.getSessionHolder().getTransaction().rollback(); + } + catch (org.hibernate.TransactionException ex) { + throw new TransactionSystemException("Could not roll back Hibernate transaction", ex); + } + catch (HibernateException ex) { + // Shouldn't really happen, as a rollback doesn't cause a flush. + throw convertHibernateAccessException(ex); + } + finally { + if (!txObject.isNewSession() && !this.hibernateManagedSession) { + // Clear all pending inserts/updates/deletes in the Session. + // Necessary for pre-bound Sessions, to avoid inconsistent state. + txObject.getSessionHolder().getSession().clear(); + } + } + } + + @Override + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Setting Hibernate transaction on Session [" + + txObject.getSessionHolder().getSession() + "] rollback-only"); + } + txObject.setRollbackOnly(); + } + + @Override + protected void doCleanupAfterCompletion(Object transaction) { + HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; + + // Remove the session holder from the thread. + if (txObject.isNewSessionHolder()) { + TransactionSynchronizationManager.unbindResource(getSessionFactory()); + } + + // Remove the JDBC connection holder from the thread, if exposed. + if (getDataSource() != null) { + TransactionSynchronizationManager.unbindResource(getDataSource()); + } + + Session session = txObject.getSessionHolder().getSession(); + if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) { + // We're running with connection release mode "on_close": We're able to reset + // the isolation level and/or read-only flag of the JDBC Connection here. + // Else, we need to rely on the connection pool to perform proper cleanup. + try { + Connection con = ((SessionImplementor) session).connection(); + DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel()); + } + catch (HibernateException ex) { + logger.debug("Could not access JDBC Connection of Hibernate Session", ex); + } + } + + if (txObject.isNewSession()) { + if (logger.isDebugEnabled()) { + logger.debug("Closing Hibernate Session [" + session + "] after transaction"); + } + SessionFactoryUtils.closeSession(session); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction"); + } + if (txObject.getSessionHolder().getPreviousFlushMode() != null) { + session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode()); + } + if (!this.hibernateManagedSession) { + session.disconnect(); + } + } + txObject.getSessionHolder().clear(); + } + + /** + * Return whether the given Hibernate Session will always hold the same + * JDBC Connection. This is used to check whether the transaction manager + * can safely prepare and clean up the JDBC Connection used for a transaction. + *

The default implementation checks the Session's connection release mode + * to be "on_close". + * @param session the Hibernate Session to check + * @see org.hibernate.engine.transaction.spi.TransactionContext#getConnectionReleaseMode() + * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE + */ + protected boolean isSameConnectionForEntireSession(Session session) { + if (!(session instanceof TransactionContext)) { + // The best we can do is to assume we're safe. + return true; + } + ConnectionReleaseMode releaseMode = ((TransactionContext) session).getConnectionReleaseMode(); + return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode); + } + + + /** + * Convert the given HibernateException to an appropriate exception + * from the org.springframework.dao hierarchy. + *

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 + */ + protected DataAccessException convertHibernateAccessException(HibernateException ex) { + return SessionFactoryUtils.convertHibernateAccessException(ex); + } + + + /** + * Hibernate transaction object, representing a SessionHolder. + * Used as transaction object by HibernateTransactionManager. + */ + private class HibernateTransactionObject extends JdbcTransactionObjectSupport { + + private SessionHolder sessionHolder; + + private boolean newSessionHolder; + + private boolean newSession; + + public void setSession(Session session) { + this.sessionHolder = new SessionHolder(session); + this.newSessionHolder = true; + this.newSession = true; + } + + public void setExistingSession(Session session) { + this.sessionHolder = new SessionHolder(session); + this.newSessionHolder = true; + this.newSession = false; + } + + public void setSessionHolder(SessionHolder sessionHolder) { + this.sessionHolder = sessionHolder; + this.newSessionHolder = false; + this.newSession = false; + } + + public SessionHolder getSessionHolder() { + return this.sessionHolder; + } + + public boolean isNewSessionHolder() { + return this.newSessionHolder; + } + + public boolean isNewSession() { + return this.newSession; + } + + public boolean hasSpringManagedTransaction() { + return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null); + } + + public boolean hasHibernateManagedTransaction() { + return (this.sessionHolder != null && this.sessionHolder.getSession().getTransaction().isActive()); + } + + public void setRollbackOnly() { + this.sessionHolder.setRollbackOnly(); + if (hasConnectionHolder()) { + getConnectionHolder().setRollbackOnly(); + } + } + + public boolean isRollbackOnly() { + return this.sessionHolder.isRollbackOnly() || + (hasConnectionHolder() && getConnectionHolder().isRollbackOnly()); + } + + @Override + public void flush() { + try { + this.sessionHolder.getSession().flush(); + } + catch (HibernateException ex) { + throw convertHibernateAccessException(ex); + } + } + } + + + /** + * Holder for suspended resources. + * Used internally by doSuspend and doResume. + */ + private static class SuspendedResourcesHolder { + + private final SessionHolder sessionHolder; + + private final ConnectionHolder connectionHolder; + + private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) { + this.sessionHolder = sessionHolder; + this.connectionHolder = conHolder; + } + + private SessionHolder getSessionHolder() { + return this.sessionHolder; + } + + private ConnectionHolder getConnectionHolder() { + return this.connectionHolder; + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java new file mode 100644 index 00000000000..083a5661951 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java @@ -0,0 +1,229 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Properties; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.MappedSuperclass; +import javax.sql.DataSource; + +import org.hibernate.MappingException; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that creates a + * Hibernate {@link org.hibernate.SessionFactory}. This is the usual way to + * set up a shared Hibernate SessionFactory in a Spring application context; + * the SessionFactory can then be passed to Hibernate-based DAOs via + * dependency injection. + * + *

NOTE: This variant of LocalSessionFactoryBean requires Hibernate 4.0 + * or higher. It is similar in role to the same-named class in the orm.hibernate3 + * package. However, in practice, it is closer to AnnotationSessionFactoryBean + * since its core purpose is to bootstrap a SessionFactory from annotation scanning. + * + * @author Juergen Hoeller + * @since 3.1 + * @see #setDataSource + * @see #setPackagesToScan + */ +public class LocalSessionFactoryBean implements FactoryBean, ResourceLoaderAware, + InitializingBean, DisposableBean { + + private static final String RESOURCE_PATTERN = "/**/*.class"; + + private static final Method addAnnotatedClassMethod = + ClassUtils.getMethodIfAvailable(Configuration.class, "addAnnotatedClass", Class.class); + + + private DataSource dataSource; + + private Properties hibernateProperties; + + private String[] packagesToScan; + + private Class[] annotatedClasses; + + private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + private TypeFilter[] entityTypeFilters = new TypeFilter[] { + new AnnotationTypeFilter(Entity.class, false), + new AnnotationTypeFilter(Embeddable.class, false), + new AnnotationTypeFilter(MappedSuperclass.class, false)}; + + private SessionFactory sessionFactory; + + + /** + * Set the DataSource to be used by the SessionFactory. + * If set, this will override corresponding settings in Hibernate properties. + *

If this is set, the Hibernate settings should not define + * a connection provider to avoid meaningless double configuration. + *

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. + *

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 HibernateTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * Set Hibernate properties, such as "hibernate.dialect". + *

Can be used to override values in a Hibernate XML config file, + * or to specify all necessary properties locally. + *

Note: Do not specify a transaction provider here when using + * Spring-driven transactions. It is also advisable to omit connection + * provider settings and use a Spring-set DataSource instead. + * @see #setDataSource + */ + public void setHibernateProperties(Properties hibernateProperties) { + this.hibernateProperties = hibernateProperties; + } + + /** + * Specify packages to search for autodetection of your entity classes in the + * classpath. This is analogous to Spring's component-scan feature + * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). + */ + public void setPackagesToScan(String... packagesToScan) { + this.packagesToScan = packagesToScan; + } + + /** + * Specify annotated entity classes to register with this Hibernate SessionFactory. + */ + public void setAnnotatedClasses(Class... annotatedClasses) { + this.annotatedClasses = annotatedClasses; + } + + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); + } + + + public void afterPropertiesSet() { + Configuration config = new Configuration(); + config.getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName()); + config.getProperties().put(Environment.DATASOURCE, this.dataSource); + config.getProperties().put("hibernate.classLoader.application", this.resourcePatternResolver.getClassLoader()); + config.addProperties(this.hibernateProperties); + scanPackages(config); + for (Class annotatedClass : this.annotatedClasses) { + ReflectionUtils.invokeMethod(addAnnotatedClassMethod, config, annotatedClass); + } + this.sessionFactory = config.buildSessionFactory(); + } + + /** + * Perform Spring-based scanning for entity classes. + * @see #setPackagesToScan + */ + private void scanPackages(Configuration config) { + if (this.packagesToScan != null) { + try { + for (String pkg : this.packagesToScan) { + String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN; + Resource[] resources = this.resourcePatternResolver.getResources(pattern); + MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); + for (Resource resource : resources) { + if (resource.isReadable()) { + MetadataReader reader = readerFactory.getMetadataReader(resource); + String className = reader.getClassMetadata().getClassName(); + if (matchesFilter(reader, readerFactory)) { + Class annotatedClass = this.resourcePatternResolver.getClassLoader().loadClass(className); + ReflectionUtils.invokeMethod(addAnnotatedClassMethod, config, annotatedClass); + } + } + } + } + } + catch (IOException ex) { + throw new MappingException("Failed to scan classpath for unlisted classes", ex); + } + catch (ClassNotFoundException ex) { + throw new MappingException("Failed to load annotated classes from classpath", ex); + } + } + } + + /** + * Check whether any of the configured entity type filters matches + * the current class descriptor contained in the metadata reader. + */ + private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { + if (this.entityTypeFilters != null) { + for (TypeFilter filter : this.entityTypeFilters) { + if (filter.match(reader, readerFactory)) { + return true; + } + } + } + return false; + } + + + public SessionFactory getObject() { + return this.sessionFactory; + } + + public Class getObjectType() { + return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class); + } + + public boolean isSingleton() { + return true; + } + + + public void destroy() { + this.sessionFactory.close(); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java new file mode 100644 index 00000000000..6c8d9e7a83b --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java @@ -0,0 +1,181 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +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.NonUniqueResultException; +import org.hibernate.ObjectDeletedException; +import org.hibernate.PersistentObjectException; +import org.hibernate.PropertyValueException; +import org.hibernate.QueryException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.StaleObjectStateException; +import org.hibernate.StaleStateException; +import org.hibernate.TransientObjectException; +import org.hibernate.UnresolvableObjectException; +import org.hibernate.WrongClassException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.exception.DataException; +import org.hibernate.exception.JDBCConnectionException; +import org.hibernate.exception.LockAcquisitionException; +import org.hibernate.exception.SQLGrammarException; +import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; + +import org.springframework.dao.CannotAcquireLockException; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.jdbc.datasource.DataSourceUtils; + +/** + * Helper class featuring methods for Hibernate Session handling. + * Also provides support for exception translation. + * + *

Used internally by {@link HibernateTransactionManager}. + * Can also be used directly in application code. + * + * @author Juergen Hoeller + * @since 3.1 + * @see HibernateExceptionTranslator + * @see HibernateTransactionManager + */ +public abstract class SessionFactoryUtils { + + /** + * Order value for TransactionSynchronization objects that clean up Hibernate Sessions. + * Returns DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100 + * to execute Session cleanup before JDBC Connection cleanup, if any. + * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER + */ + public static final int SESSION_SYNCHRONIZATION_ORDER = + DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100; + + static final Log logger = LogFactory.getLog(SessionFactoryUtils.class); + + + /** + * Determine the DataSource of the given SessionFactory. + * @param sessionFactory the SessionFactory to check + * @return the DataSource, or null if none found + * @see org.hibernate.engine.spi.SessionFactoryImplementor#getConnectionProvider + */ + public static DataSource getDataSource(SessionFactory sessionFactory) { + if (sessionFactory instanceof SessionFactoryImplementor) { + ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getConnectionProvider(); + return cp.unwrap(DataSource.class); + } + return null; + } + + /** + * Perform actual closing of the Hibernate Session, + * catching and logging any cleanup exceptions thrown. + * @param session the Hibernate Session to close (may be null) + * @see org.hibernate.Session#close() + */ + public static void closeSession(Session session) { + if (session != null) { + try { + session.close(); + } + catch (HibernateException ex) { + logger.debug("Could not close Hibernate Session", ex); + } + catch (Throwable ex) { + logger.debug("Unexpected exception on closing Hibernate Session", ex); + } + } + } + + /** + * Convert the given HibernateException to an appropriate exception + * from the org.springframework.dao hierarchy. + * @param ex HibernateException that occured + * @return the corresponding DataAccessException instance + * @see HibernateExceptionTranslator#convertHibernateAccessException + * @see HibernateTransactionManager#convertHibernateAccessException + */ + public static DataAccessException convertHibernateAccessException(HibernateException ex) { + if (ex instanceof JDBCConnectionException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof SQLGrammarException) { + SQLGrammarException jdbcEx = (SQLGrammarException) ex; + return new InvalidDataAccessResourceUsageException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); + } + if (ex instanceof LockAcquisitionException) { + LockAcquisitionException jdbcEx = (LockAcquisitionException) ex; + return new CannotAcquireLockException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); + } + if (ex instanceof ConstraintViolationException) { + ConstraintViolationException jdbcEx = (ConstraintViolationException) ex; + return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + + "]; constraint [" + jdbcEx.getConstraintName() + "]", ex); + } + if (ex instanceof DataException) { + DataException jdbcEx = (DataException) ex; + return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); + } + if (ex instanceof JDBCException) { + return new HibernateJdbcException((JDBCException) ex); + } + if (ex instanceof PropertyValueException) { + return new DataIntegrityViolationException(ex.getMessage(), ex); + } + if (ex instanceof PersistentObjectException) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + if (ex instanceof TransientObjectException) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + if (ex instanceof ObjectDeletedException) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + if (ex instanceof QueryException) { + return new HibernateQueryException((QueryException) ex); + } + if (ex instanceof UnresolvableObjectException) { + return new HibernateObjectRetrievalFailureException((UnresolvableObjectException) ex); + } + if (ex instanceof WrongClassException) { + return new HibernateObjectRetrievalFailureException((WrongClassException) ex); + } + if (ex instanceof NonUniqueResultException) { + return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex); + } + if (ex instanceof StaleObjectStateException) { + return new HibernateOptimisticLockingFailureException((StaleObjectStateException) ex); + } + if (ex instanceof StaleStateException) { + return new HibernateOptimisticLockingFailureException((StaleStateException) ex); + } + + // fallback + return new HibernateSystemException(ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionHolder.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionHolder.java new file mode 100644 index 00000000000..d9ff38cdc5c --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SessionHolder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.FlushMode; +import org.hibernate.Session; +import org.hibernate.Transaction; + +import org.springframework.transaction.support.ResourceHolderSupport; +import org.springframework.util.Assert; + +/** + * Session holder, wrapping a Hibernate Session and a Hibernate Transaction. + * HibernateTransactionManager binds instances of this class to the thread, + * for a given SessionFactory. + * + *

Note: This is an SPI class, not intended to be used by applications. + * + * @author Juergen Hoeller + * @since 3.1 + * @see HibernateTransactionManager + * @see SessionFactoryUtils + */ +public class SessionHolder extends ResourceHolderSupport { + + private Session session; + + private Transaction transaction; + + private FlushMode previousFlushMode; + + + public SessionHolder(Session session) { + Assert.notNull(session, "Session must not be null"); + this.session = session; + } + + public Session getSession() { + return this.session; + } + + public void setTransaction(Transaction transaction) { + this.transaction = transaction; + } + + public Transaction getTransaction() { + return this.transaction; + } + + public void setPreviousFlushMode(FlushMode previousFlushMode) { + this.previousFlushMode = previousFlushMode; + } + + public FlushMode getPreviousFlushMode() { + return this.previousFlushMode; + } + + + @Override + public void clear() { + super.clear(); + this.session = null; + this.transaction = null; + this.previousFlushMode = null; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringFlushSynchronization.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringFlushSynchronization.java new file mode 100644 index 00000000000..19edea3cd97 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringFlushSynchronization.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.HibernateException; +import org.hibernate.Session; + +import org.springframework.transaction.support.TransactionSynchronizationAdapter; + +/** + * Simple synchronization adapter that propagates a flush() call + * to the underlying Hibernate Session. Used in combination with JTA. + * + * @author Juergen Hoeller + * @since 3.1 + */ +class SpringFlushSynchronization extends TransactionSynchronizationAdapter { + + private final Session session; + + + public SpringFlushSynchronization(Session session) { + this.session = session; + } + + + @Override + public void flush() { + try { + SessionFactoryUtils.logger.debug("Flushing Hibernate Session on explicit request"); + this.session.flush(); + } + catch (HibernateException ex) { + throw SessionFactoryUtils.convertHibernateAccessException(ex); + } + } + + + @Override + public boolean equals(Object obj) { + return (obj instanceof SpringFlushSynchronization && + this.session == ((SpringFlushSynchronization) obj).session); + } + + @Override + public int hashCode() { + return this.session.hashCode(); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringJtaSessionContext.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringJtaSessionContext.java new file mode 100644 index 00000000000..e77d36cac69 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringJtaSessionContext.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.FlushMode; +import org.hibernate.Session; +import org.hibernate.context.internal.JTASessionContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Spring-specific subclass of Hibernate's JTASessionContext, + * setting FlushMode.MANUAL for read-only transactions. + * + * @author Juergen Hoeller + * @since 3.1 + */ +public class SpringJtaSessionContext extends JTASessionContext { + + public SpringJtaSessionContext(SessionFactoryImplementor factory) { + super(factory); + } + + @Override + protected Session buildOrObtainSession() { + Session session = super.buildOrObtainSession(); + if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + session.setFlushMode(FlushMode.MANUAL); + } + return session; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java new file mode 100644 index 00000000000..ffbca16e407 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import javax.transaction.TransactionManager; + +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.context.spi.CurrentSessionContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.service.jta.platform.spi.JtaPlatform; + +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Implementation of Hibernate 3.1's CurrentSessionContext interface + * that delegates to Spring's SessionFactoryUtils for providing a + * Spring-managed current Session. + * + *

This CurrentSessionContext implementation can also be specified in custom + * SessionFactory setup through the "hibernate.current_session_context_class" + * property, with the fully qualified name of this class as value. + * + * @author Juergen Hoeller + * @since 3.1 + */ +@SuppressWarnings("serial") +public class SpringSessionContext implements CurrentSessionContext { + + private final SessionFactoryImplementor sessionFactory; + + private final CurrentSessionContext jtaSessionContext; + + + /** + * Create a new SpringSessionContext for the given Hibernate SessionFactory. + * @param sessionFactory the SessionFactory to provide current Sessions for + */ + public SpringSessionContext(SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform.class); + TransactionManager transactionManager = jtaPlatform.retrieveTransactionManager(); + this.jtaSessionContext = (transactionManager != null ? new SpringJtaSessionContext(sessionFactory) : null); + } + + + /** + * Retrieve the Spring-managed Session for the current thread, if any. + */ + public Session currentSession() throws HibernateException { + Object value = TransactionSynchronizationManager.getResource(this.sessionFactory); + if (value instanceof Session) { + return (Session) value; + } + else if (value instanceof SessionHolder) { + SessionHolder sessionHolder = (SessionHolder) value; + Session session = sessionHolder.getSession(); + if (TransactionSynchronizationManager.isSynchronizationActive() && + !sessionHolder.isSynchronizedWithTransaction()) { + TransactionSynchronizationManager.registerSynchronization( + new SpringSessionSynchronization(sessionHolder, this.sessionFactory)); + sessionHolder.setSynchronizedWithTransaction(true); + // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session + // with FlushMode.MANUAL, which needs to allow flushing within the transaction. + FlushMode flushMode = session.getFlushMode(); + if (FlushMode.isManualFlushMode(flushMode) && + !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + session.setFlushMode(FlushMode.AUTO); + sessionHolder.setPreviousFlushMode(flushMode); + } + } + return session; + } + else if (this.jtaSessionContext != null) { + Session session = this.jtaSessionContext.currentSession(); + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session)); + } + return session; + } + else { + throw new HibernateException("No Session found for current thread"); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionSynchronization.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionSynchronization.java new file mode 100644 index 00000000000..fa49ace3f74 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/SpringSessionSynchronization.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2011 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.hibernate4; + +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import org.springframework.core.Ordered; +import org.springframework.dao.DataAccessException; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Callback for resource cleanup at the end of a Spring-managed transaction + * for a pre-bound Hibernate Session. + * + * @author Juergen Hoeller + * @since 3.1 + */ +class SpringSessionSynchronization implements TransactionSynchronization, Ordered { + + private final SessionHolder sessionHolder; + + private final SessionFactory sessionFactory; + + private boolean holderActive = true; + + + public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory) { + this.sessionHolder = sessionHolder; + this.sessionFactory = sessionFactory; + } + + private Session getCurrentSession() { + return this.sessionHolder.getSession(); + } + + + public int getOrder() { + return SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER; + } + + public void suspend() { + if (this.holderActive) { + TransactionSynchronizationManager.unbindResource(this.sessionFactory); + // Eagerly disconnect the Session here, to make release mode "on_close" work on JBoss. + getCurrentSession().disconnect(); + } + } + + public void resume() { + if (this.holderActive) { + TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); + } + } + + public void flush() { + try { + SessionFactoryUtils.logger.debug("Flushing Hibernate Session on explicit request"); + getCurrentSession().flush(); + } + catch (HibernateException ex) { + throw SessionFactoryUtils.convertHibernateAccessException(ex); + } + } + + public void beforeCommit(boolean readOnly) throws DataAccessException { + if (!readOnly) { + Session session = getCurrentSession(); + // Read-write transaction -> flush the Hibernate Session. + // Further check: only flush when not FlushMode.MANUAL. + if (!FlushMode.isManualFlushMode(session.getFlushMode())) { + try { + SessionFactoryUtils.logger.debug("Flushing Hibernate Session on transaction synchronization"); + session.flush(); + } + catch (HibernateException ex) { + throw SessionFactoryUtils.convertHibernateAccessException(ex); + } + } + } + } + + public void beforeCompletion() { + Session session = this.sessionHolder.getSession(); + if (this.sessionHolder.getPreviousFlushMode() != null) { + // In case of pre-bound Session, restore previous flush mode. + session.setFlushMode(this.sessionHolder.getPreviousFlushMode()); + } + // Eagerly disconnect the Session here, to make release mode "on_close" work nicely. + session.disconnect(); + } + + public void afterCommit() { + } + + public void afterCompletion(int status) { + if (status != STATUS_COMMITTED) { + // Clear all pending inserts/updates/deletes in the Session. + // Necessary for pre-bound Sessions, to avoid inconsistent state. + this.sessionHolder.getSession().clear(); + } + this.sessionHolder.setSynchronizedWithTransaction(false); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/package-info.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/package-info.java new file mode 100644 index 00000000000..86f12a5b38f --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/package-info.java @@ -0,0 +1,17 @@ + +/** + * + * Package providing integration of + * Hibernate 4.0 + * with Spring concepts. + * + *

Contains an implementation of Spring's transaction SPI for local Hibernate transactions. + * This package is intentionally rather minimal, relying on native Hibernate builder APIs + * for building a SessionFactory (for example in an @Bean method in a @Configuration class). + * + *

This package supports Hibernate 4.x only. + * See the org.springframework.orm.hibernate3 package for Hibernate 3.x support. + * + */ +package org.springframework.orm.hibernate4; + diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java new file mode 100644 index 00000000000..29ec3925ad5 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2011 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.hibernate4.support; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.orm.hibernate4.SessionFactoryUtils; +import org.springframework.orm.hibernate4.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire + * processing of the request. Intended for the "Open Session in View" pattern, + * i.e. to allow for lazy loading in web views despite the original transactions + * already being completed. + * + *

This filter makes Hibernate Sessions available via the current thread, which + * will be autodetected by transaction managers. It is suitable for service layer + * transactions via {@link org.springframework.orm.hibernate4.HibernateTransactionManager} + * as well as for non-transactional execution (if configured appropriately). + * + *

NOTE: This filter will by default not flush the Hibernate Session, + * with the flush mode set to FlushMode.NEVER. It assumes to be used + * in combination with service layer transactions that care for the flushing: The + * active transaction manager will temporarily change the flush mode to + * FlushMode.AUTO during a read-write transaction, with the flush + * mode reset to FlushMode.NEVER at the end of each transaction. + * + *

WARNING: Applying this filter to existing logic can cause issues that + * have not appeared before, through the use of a single Hibernate Session for the + * processing of an entire request. In particular, the reassociation of persistent + * objects with a Hibernate Session has to occur at the very beginning of request + * processing, to avoid clashes with already loaded instances of the same objects. + * + *

Looks up the SessionFactory in Spring's root web application context. + * Supports a "sessionFactoryBeanName" filter init-param in web.xml; + * the default bean name is "sessionFactory". Looks up the SessionFactory on each + * request, to avoid initialization order issues (when using ContextLoaderServlet, + * the root application context will get initialized after this filter). + * + * @author Juergen Hoeller + * @since 3.1 + * @see #lookupSessionFactory + * @see OpenSessionInViewInterceptor + * @see org.springframework.orm.hibernate4.HibernateTransactionManager + * @see org.springframework.transaction.support.TransactionSynchronizationManager + */ +public class OpenSessionInViewFilter extends OncePerRequestFilter { + + public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory"; + + private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME; + + + /** + * Set the bean name of the SessionFactory to fetch from Spring's + * root application context. Default is "sessionFactory". + * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME + */ + public void setSessionFactoryBeanName(String sessionFactoryBeanName) { + this.sessionFactoryBeanName = sessionFactoryBeanName; + } + + /** + * Return the bean name of the SessionFactory to fetch from Spring's + * root application context. + */ + protected String getSessionFactoryBeanName() { + return this.sessionFactoryBeanName; + } + + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + SessionFactory sessionFactory = lookupSessionFactory(request); + boolean participate = false; + + if (TransactionSynchronizationManager.hasResource(sessionFactory)) { + // Do not modify the Session: just set the participate flag. + participate = true; + } + else { + logger.debug("Opening Hibernate Session in OpenSessionInViewFilter"); + Session session = openSession(sessionFactory); + TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); + } + + try { + filterChain.doFilter(request, response); + } + + finally { + if (!participate) { + SessionHolder sessionHolder = + (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory); + logger.debug("Closing Hibernate Session in OpenSessionInViewFilter"); + SessionFactoryUtils.closeSession(sessionHolder.getSession()); + } + } + } + + /** + * Look up the SessionFactory that this filter should use, + * taking the current HTTP request as argument. + *

The default implementation delegates to the {@link #lookupSessionFactory()} + * variant without arguments. + * @param request the current request + * @return the SessionFactory to use + */ + protected SessionFactory lookupSessionFactory(HttpServletRequest request) { + return lookupSessionFactory(); + } + + /** + * Look up the SessionFactory that this filter should use. + *

The default implementation looks for a bean with the specified name + * in Spring's root application context. + * @return the SessionFactory to use + * @see #getSessionFactoryBeanName + */ + protected SessionFactory lookupSessionFactory() { + if (logger.isDebugEnabled()) { + logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter"); + } + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + return wac.getBean(getSessionFactoryBeanName(), SessionFactory.class); + } + + /** + * Open a Session for the SessionFactory that this filter uses. + *

The default implementation delegates to the + * SessionFactory.openSession method and + * sets the Session's flush mode to "MANUAL". + * @param sessionFactory the SessionFactory that this filter uses + * @return the Session to use + * @throws DataAccessResourceFailureException if the Session could not be created + * @see org.hibernate.FlushMode#MANUAL + */ + protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException { + try { + Session session = sessionFactory.openSession(); + session.setFlushMode(FlushMode.MANUAL); + return session; + } + catch (HibernateException ex) { + throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java new file mode 100644 index 00000000000..fd7d76abbc8 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java @@ -0,0 +1,176 @@ +/* + * Copyright 2002-2011 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.hibernate4.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.orm.hibernate4.SessionFactoryUtils; +import org.springframework.orm.hibernate4.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +/** + * Spring web request interceptor that binds a Hibernate Session to the + * thread for the entire processing of the request. + * + *

This class is a concrete expression of the "Open Session in View" pattern, which + * is a pattern that allows for the lazy loading of associations in web views despite + * the original transactions already being completed. + * + *

This interceptor makes Hibernate Sessions available via the current thread, + * which will be autodetected by transaction managers. It is suitable for service layer + * transactions via {@link org.springframework.orm.hibernate4.HibernateTransactionManager} + * as well as for non-transactional execution (if configured appropriately). + * + *

NOTE: This interceptor will by default not flush the Hibernate + * Session, with the flush mode being set to FlushMode.NEVER. + * It assumes that it will be used in combination with service layer transactions + * that handle the flushing: the active transaction manager will temporarily change + * the flush mode to FlushMode.AUTO during a read-write transaction, + * with the flush mode reset to FlushMode.NEVER at the end of each + * transaction. If you intend to use this interceptor without transactions, consider + * changing the default flush mode (through the {@link #setFlushMode "flushMode"} property). + * + *

In contrast to {@link OpenSessionInViewFilter}, this interceptor is configured + * in a Spring application context and can thus take advantage of bean wiring.. + * + *

WARNING: Applying this interceptor to existing logic can cause issues + * that have not appeared before, through the use of a single Hibernate + * Session for the processing of an entire request. In particular, the + * reassociation of persistent objects with a Hibernate Session has to + * occur at the very beginning of request processing, to avoid clashes with already + * loaded instances of the same objects. + * + * @author Juergen Hoeller + * @since 3.1 + * @see #setSingleSession + * @see #setFlushMode + * @see OpenSessionInViewFilter + * @see org.springframework.orm.hibernate4.HibernateTransactionManager + * @see org.springframework.transaction.support.TransactionSynchronizationManager + */ +public class OpenSessionInViewInterceptor implements WebRequestInterceptor { + + /** + * Suffix that gets appended to the SessionFactory + * toString() representation for the "participate in existing + * session handling" request attribute. + * @see #getParticipateAttributeName + */ + public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE"; + + protected final Log logger = LogFactory.getLog(getClass()); + + private SessionFactory sessionFactory; + + + public void setSessionFactory(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + public SessionFactory getSessionFactory() { + return this.sessionFactory; + } + + + /** + * Open a new Hibernate Session according to the settings of this + * HibernateAccessor and bind it to the thread via the + * {@link org.springframework.transaction.support.TransactionSynchronizationManager}. + */ + public void preHandle(WebRequest request) throws DataAccessException { + if (TransactionSynchronizationManager.hasResource(getSessionFactory())) { + // Do not modify the Session: just mark the request accordingly. + String participateAttributeName = getParticipateAttributeName(); + Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + int newCount = (count != null ? count + 1 : 1); + request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST); + } + else { + logger.debug("Opening Hibernate Session in OpenSessionInViewInterceptor"); + Session session = openSession(); + TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session)); + } + } + + public void postHandle(WebRequest request, ModelMap model) { + } + + /** + * Unbind the Hibernate Session from the thread and close it). + * @see org.springframework.transaction.support.TransactionSynchronizationManager + */ + public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException { + String participateAttributeName = getParticipateAttributeName(); + Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + if (count != null) { + // Do not modify the Session: just clear the marker. + if (count > 1) { + request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST); + } + else { + request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + } + } + else { + SessionHolder sessionHolder = + (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory()); + logger.debug("Closing Hibernate Session in OpenSessionInViewInterceptor"); + SessionFactoryUtils.closeSession(sessionHolder.getSession()); + } + } + + /** + * Open a Session for the SessionFactory that this interceptor uses. + *

The default implementation delegates to the + * SessionFactory.openSession method and + * sets the Session's flush mode to "MANUAL". + * @return the Session to use + * @throws DataAccessResourceFailureException if the Session could not be created + * @see org.hibernate.FlushMode#MANUAL + */ + protected Session openSession() throws DataAccessResourceFailureException { + try { + Session session = getSessionFactory().openSession(); + session.setFlushMode(FlushMode.MANUAL); + return session; + } + catch (HibernateException ex) { + throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); + } + } + + /** + * Return the name of the request attribute that identifies that a request is + * already intercepted. + *

The default implementation takes the toString() representation + * of the SessionFactory instance and appends {@link #PARTICIPATE_SUFFIX}. + */ + protected String getParticipateAttributeName() { + return getSessionFactory().toString() + PARTICIPATE_SUFFIX; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/package-info.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/package-info.java new file mode 100644 index 00000000000..10cd7a2758a --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/support/package-info.java @@ -0,0 +1,24 @@ + +/* + * Copyright 2002-2011 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. + */ + +/** + * + * Classes supporting the org.springframework.orm.hibernate4 package. + * + */ +package org.springframework.orm.hibernate4.support; +