committed initial Hibernate 4.0 support
This commit is contained in:
parent
55f91e54c4
commit
0ff83606df
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
* <code>org.springframework.dao</code> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
* <code>SessionFactory.getCurrentSession()</code> is required for Hibernate
|
||||||
|
* access code that needs to support this transaction handling mechanism,
|
||||||
|
* with the SessionFactory being configured with {@link SpringSessionContext}.
|
||||||
|
*
|
||||||
|
* <p>Supports custom isolation levels, and timeouts that get applied as
|
||||||
|
* Hibernate transaction timeouts.
|
||||||
|
*
|
||||||
|
* <p>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}).
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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).
|
||||||
|
*
|
||||||
|
* <p>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). <i>Note that Hibernate itself does not
|
||||||
|
* support nested transactions! Hence, do not expect Hibernate access code to
|
||||||
|
* semantically participate in a nested transaction.</i>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* <p>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.
|
||||||
|
* <p>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.
|
||||||
|
* <p>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 <code>setDataSource</code>. Default is "true".
|
||||||
|
* <p>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.
|
||||||
|
* <p>Default is "true". If you turn this flag off, the transaction manager
|
||||||
|
* will not support per-transaction isolation levels anymore. It will not
|
||||||
|
* call <code>Connection.setReadOnly(true)</code> 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).
|
||||||
|
* <p>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.
|
||||||
|
* <p>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 <code>getCurrentSession()</code> call fails.
|
||||||
|
* <p>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 <i>not</i>
|
||||||
|
* receive a <code>clear()</code> call (on rollback) or a <code>disconnect()</code>
|
||||||
|
* 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.
|
||||||
|
* <p>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 <code>org.springframework.dao</code> hierarchy.
|
||||||
|
* <p>Will automatically apply a specified SQLExceptionTranslator to a
|
||||||
|
* Hibernate JDBCException, else rely on Hibernate's default translation.
|
||||||
|
* @param ex HibernateException that occured
|
||||||
|
* @return a corresponding DataAccessException
|
||||||
|
* @see SessionFactoryUtils#convertHibernateAccessException
|
||||||
|
*/
|
||||||
|
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 <code>doSuspend</code> and <code>doResume</code>.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> This variant of LocalSessionFactoryBean requires Hibernate 4.0
|
||||||
|
* or higher. It is similar in role to the same-named class in the <code>orm.hibernate3</code>
|
||||||
|
* package. However, in practice, it is closer to <code>AnnotationSessionFactoryBean</code>
|
||||||
|
* since its core purpose is to bootstrap a <code>SessionFactory</code> from annotation scanning.
|
||||||
|
*
|
||||||
|
* @author Juergen Hoeller
|
||||||
|
* @since 3.1
|
||||||
|
* @see #setDataSource
|
||||||
|
* @see #setPackagesToScan
|
||||||
|
*/
|
||||||
|
public class LocalSessionFactoryBean implements FactoryBean<SessionFactory>, 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.
|
||||||
|
* <p>If this is set, the Hibernate settings should not define
|
||||||
|
* a connection provider to avoid meaningless double configuration.
|
||||||
|
* <p>If using HibernateTransactionManager as transaction strategy, consider
|
||||||
|
* proxying your target DataSource with a LazyConnectionDataSourceProxy.
|
||||||
|
* This defers fetching of an actual JDBC Connection until the first JDBC
|
||||||
|
* Statement gets executed, even within JDBC transactions (as performed by
|
||||||
|
* HibernateTransactionManager). Such lazy fetching is particularly beneficial
|
||||||
|
* for read-only operations, in particular if the chances of resolving the
|
||||||
|
* result in the second-level cache are high.
|
||||||
|
* <p>As JTA and transactional JNDI DataSources already provide lazy enlistment
|
||||||
|
* of JDBC Connections, LazyConnectionDataSourceProxy does not add value with
|
||||||
|
* JTA (i.e. Spring's JtaTransactionManager) as transaction strategy.
|
||||||
|
* @see 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".
|
||||||
|
* <p>Can be used to override values in a Hibernate XML config file,
|
||||||
|
* or to specify all necessary properties locally.
|
||||||
|
* <p>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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>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 <code>DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100</code>
|
||||||
|
* 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 <code>null</code> 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 <code>null</code>)
|
||||||
|
* @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 <code>org.springframework.dao</code> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <code>flush()</code> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <code>FlushMode.MANUAL</code> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Package providing integration of
|
||||||
|
* <a href="http://www.hibernate.org">Hibernate 4.0</a>
|
||||||
|
* with Spring concepts.
|
||||||
|
*
|
||||||
|
* <p>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).
|
||||||
|
*
|
||||||
|
* <p><b>This package supports Hibernate 4.x only.</b>
|
||||||
|
* See the org.springframework.orm.hibernate3 package for Hibernate 3.x support.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.springframework.orm.hibernate4;
|
||||||
|
|
||||||
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>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).
|
||||||
|
*
|
||||||
|
* <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session,
|
||||||
|
* with the flush mode set to <code>FlushMode.NEVER</code>. 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
|
||||||
|
* <code>FlushMode.AUTO</code> during a read-write transaction, with the flush
|
||||||
|
* mode reset to <code>FlushMode.NEVER</code> at the end of each transaction.
|
||||||
|
*
|
||||||
|
* <p><b>WARNING:</b> 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.
|
||||||
|
*
|
||||||
|
* <p>Looks up the SessionFactory in Spring's root web application context.
|
||||||
|
* Supports a "sessionFactoryBeanName" filter init-param in <code>web.xml</code>;
|
||||||
|
* 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 <i>after</i> 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.
|
||||||
|
* <p>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.
|
||||||
|
* <p>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.
|
||||||
|
* <p>The default implementation delegates to the
|
||||||
|
* <code>SessionFactory.openSession</code> method and
|
||||||
|
* sets the <code>Session</code>'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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <code>Session</code> to the
|
||||||
|
* thread for the entire processing of the request.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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).
|
||||||
|
*
|
||||||
|
* <p><b>NOTE</b>: This interceptor will by default <i>not</i> flush the Hibernate
|
||||||
|
* <code>Session</code>, with the flush mode being set to <code>FlushMode.NEVER</code>.
|
||||||
|
* 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 <code>FlushMode.AUTO</code> during a read-write transaction,
|
||||||
|
* with the flush mode reset to <code>FlushMode.NEVER</code> 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).
|
||||||
|
*
|
||||||
|
* <p>In contrast to {@link OpenSessionInViewFilter}, this interceptor is configured
|
||||||
|
* in a Spring application context and can thus take advantage of bean wiring..
|
||||||
|
*
|
||||||
|
* <p><b>WARNING:</b> Applying this interceptor to existing logic can cause issues
|
||||||
|
* that have not appeared before, through the use of a single Hibernate
|
||||||
|
* <code>Session</code> for the processing of an entire request. In particular, the
|
||||||
|
* reassociation of persistent objects with a Hibernate <code>Session</code> 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 <code>SessionFactory</code>
|
||||||
|
* <code>toString()</code> 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 <code>Session</code> according to the settings of this
|
||||||
|
* <code>HibernateAccessor</code> 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 <code>Session</code> 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.
|
||||||
|
* <p>The default implementation delegates to the
|
||||||
|
* <code>SessionFactory.openSession</code> method and
|
||||||
|
* sets the <code>Session</code>'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.
|
||||||
|
* <p>The default implementation takes the <code>toString()</code> representation
|
||||||
|
* of the <code>SessionFactory</code> instance and appends {@link #PARTICIPATE_SUFFIX}.
|
||||||
|
*/
|
||||||
|
protected String getParticipateAttributeName() {
|
||||||
|
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <code>org.springframework.orm.hibernate4</code> package.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.springframework.orm.hibernate4.support;
|
||||||
|
|
||||||
Loading…
Reference in New Issue