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 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 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 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 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 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
+ * 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
+ * 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
+ * The default implementation delegates to the
+ * The default implementation takes 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 FactoryBeanDataSourceUtils.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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * 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.
+ *
+ * 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).
+ *
+ * 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.
+ * 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.
+ * 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;
+