diff --git a/org.springframework.orm/build.xml b/org.springframework.orm/build.xml
new file mode 100644
index 00000000000..ac4c8153a52
--- /dev/null
+++ b/org.springframework.orm/build.xml
@@ -0,0 +1,6 @@
+
+
This class implements the + * {@link org.springframework.dao.support.PersistenceExceptionTranslator} + * interface, as autodetected by Spring's + * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}, + * for AOP-based translation of native exceptions to Spring DataAccessExceptions. + * Hence, the presence of e.g. LocalSessionFactoryBean automatically enables + * a PersistenceExceptionTranslationPostProcessor to translate Hibernate exceptions. + * + *
This class mainly serves as common base class for {@link LocalSessionFactoryBean}. + * For details on typical SessionFactory setup, see the LocalSessionFactoryBean javadoc. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #setExposeTransactionAwareSessionFactory + * @see org.hibernate.SessionFactory#getCurrentSession() + * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor + */ +public abstract class AbstractSessionFactoryBean + implements FactoryBean, InitializingBean, DisposableBean, PersistenceExceptionTranslator { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private DataSource dataSource; + + private boolean useTransactionAwareDataSource = false; + + private boolean exposeTransactionAwareSessionFactory = true; + + private SQLExceptionTranslator jdbcExceptionTranslator; + + private SessionFactory sessionFactory; + + + /** + * Set the DataSource to be used by the SessionFactory. + * If set, this will override corresponding settings in Hibernate properties. + *
If this is set, the Hibernate settings should not define + * a connection provider to avoid meaningless double configuration. + *
If using HibernateTransactionManager as transaction strategy, consider + * proxying your target DataSource with a LazyConnectionDataSourceProxy. + * This defers fetching of an actual JDBC Connection until the first JDBC + * Statement gets executed, even within JDBC transactions (as performed by + * HibernateTransactionManager). Such lazy fetching is particularly beneficial + * for read-only operations, in particular if the chances of resolving the + * result in the second-level cache are high. + *
As JTA and transactional JNDI DataSources already provide lazy enlistment + * of JDBC Connections, LazyConnectionDataSourceProxy does not add value with + * JTA (i.e. Spring's JtaTransactionManager) as transaction strategy. + * @see #setUseTransactionAwareDataSource + * @see HibernateTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * Return the DataSource to be used by the SessionFactory. + */ + public DataSource getDataSource() { + return this.dataSource; + } + + /** + * Set whether to use a transaction-aware DataSource for the SessionFactory, + * i.e. whether to automatically wrap the passed-in DataSource with Spring's + * TransactionAwareDataSourceProxy. + *
Default is "false": LocalSessionFactoryBean is usually used with Spring's + * HibernateTransactionManager or JtaTransactionManager, both of which work nicely + * on a plain JDBC DataSource. Hibernate Sessions and their JDBC Connections are + * fully managed by the Hibernate/JTA transaction infrastructure in such a scenario. + *
If you switch this flag to "true", Spring's Hibernate access will be able to + * participate in JDBC-based transactions managed outside of Hibernate + * (for example, by Spring's DataSourceTransactionManager). This can be convenient + * if you need a different local transaction strategy for another O/R mapping tool, + * for example, but still want Hibernate access to join into those transactions. + *
A further benefit of this option is that plain Sessions opened directly
+ * via the SessionFactory, outside of Spring's Hibernate support, will still
+ * participate in active Spring-managed transactions. However, consider using
+ * Hibernate's getCurrentSession() method instead (see javadoc of
+ * "exposeTransactionAwareSessionFactory" property).
+ *
WARNING: When using a transaction-aware JDBC DataSource in combination + * with OpenSessionInViewFilter/Interceptor, whether participating in JTA or + * external JDBC-based transactions, it is strongly recommended to set Hibernate's + * Connection release mode to "after_transaction" or "after_statement", which + * guarantees proper Connection handling in such a scenario. In contrast to that, + * HibernateTransactionManager generally requires release mode "on_close". + *
Note: If you want to use Hibernate's Connection release mode "after_statement"
+ * with a DataSource specified on this LocalSessionFactoryBean (for example, a
+ * JTA-aware DataSource fetched from JNDI), switch this setting to "true".
+ * Otherwise, the ConnectionProvider used underneath will vote against aggressive
+ * release and thus silently switch to release mode "after_transaction".
+ * @see #setDataSource
+ * @see #setExposeTransactionAwareSessionFactory
+ * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
+ * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
+ * @see HibernateTransactionManager
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ */
+ public void setUseTransactionAwareDataSource(boolean useTransactionAwareDataSource) {
+ this.useTransactionAwareDataSource = useTransactionAwareDataSource;
+ }
+
+ /**
+ * Return whether to use a transaction-aware DataSource for the SessionFactory.
+ */
+ protected boolean isUseTransactionAwareDataSource() {
+ return this.useTransactionAwareDataSource;
+ }
+
+ /**
+ * Set whether to expose a transaction-aware current Session from the
+ * SessionFactory's getCurrentSession() method, returning the
+ * Session that's associated with the current Spring-managed transaction, if any.
+ *
Default is "true", letting data access code work with the plain
+ * Hibernate SessionFactory and its getCurrentSession() method,
+ * while still being able to participate in current Spring-managed transactions:
+ * with any transaction management strategy, either local or JTA / EJB CMT,
+ * and any transaction synchronization mechanism, either Spring or JTA.
+ * Furthermore, getCurrentSession() will also seamlessly work with
+ * a request-scoped Session managed by OpenSessionInViewFilter/Interceptor.
+ *
Turn this flag off to expose the plain Hibernate SessionFactory with
+ * Hibernate's default getCurrentSession() behavior, supporting
+ * plain JTA synchronization only. Alternatively, simply override the
+ * corresponding Hibernate property "hibernate.current_session_context_class".
+ * @see SpringSessionContext
+ * @see org.hibernate.SessionFactory#getCurrentSession()
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ * @see HibernateTransactionManager
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
+ */
+ public void setExposeTransactionAwareSessionFactory(boolean exposeTransactionAwareSessionFactory) {
+ this.exposeTransactionAwareSessionFactory = exposeTransactionAwareSessionFactory;
+ }
+
+ /**
+ * Return whether to expose a transaction-aware proxy for the SessionFactory.
+ */
+ protected boolean isExposeTransactionAwareSessionFactory() {
+ return this.exposeTransactionAwareSessionFactory;
+ }
+
+ /**
+ * Set the JDBC exception translator for the SessionFactory,
+ * exposed via the PersistenceExceptionTranslator interface.
+ *
Applied to any SQLException root cause of a Hibernate JDBCException, + * overriding Hibernate's default SQLException translation (which is + * based on Hibernate's Dialect for a specific target database). + * @param jdbcExceptionTranslator the exception translator + * @see java.sql.SQLException + * @see org.hibernate.JDBCException + * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator + * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator + * @see org.springframework.dao.support.PersistenceExceptionTranslator + */ + public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { + this.jdbcExceptionTranslator = jdbcExceptionTranslator; + } + + + /** + * Build and expose the SessionFactory. + * @see #buildSessionFactory() + * @see #wrapSessionFactoryIfNecessary + */ + public void afterPropertiesSet() throws Exception { + SessionFactory rawSf = buildSessionFactory(); + this.sessionFactory = wrapSessionFactoryIfNecessary(rawSf); + afterSessionFactoryCreation(); + } + + /** + * Wrap the given SessionFactory with a proxy, if demanded. + *
The default implementation simply returns the given SessionFactory as-is.
+ * Subclasses may override this to implement transaction awareness through
+ * a SessionFactory proxy, for example.
+ * @param rawSf the raw SessionFactory as built by {@link #buildSessionFactory()}
+ * @return the SessionFactory reference to expose
+ * @see #buildSessionFactory()
+ */
+ protected SessionFactory wrapSessionFactoryIfNecessary(SessionFactory rawSf) {
+ return rawSf;
+ }
+
+ /**
+ * Return the exposed SessionFactory.
+ * Will throw an exception if not initialized yet.
+ * @return the SessionFactory (never null)
+ * @throws IllegalStateException if the SessionFactory has not been initialized yet
+ */
+ protected final SessionFactory getSessionFactory() {
+ if (this.sessionFactory == null) {
+ throw new IllegalStateException("SessionFactory not initialized yet");
+ }
+ return this.sessionFactory;
+ }
+
+ /**
+ * Close the SessionFactory on bean factory shutdown.
+ */
+ public void destroy() throws HibernateException {
+ logger.info("Closing Hibernate SessionFactory");
+ try {
+ beforeSessionFactoryDestruction();
+ }
+ finally {
+ this.sessionFactory.close();
+ }
+ }
+
+
+ /**
+ * Return the singleton SessionFactory.
+ */
+ public Object getObject() {
+ return this.sessionFactory;
+ }
+
+ public Class getObjectType() {
+ return (this.sessionFactory != null) ? this.sessionFactory.getClass() : SessionFactory.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+
+ /**
+ * Implementation of the PersistenceExceptionTranslator interface,
+ * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
+ *
Converts the exception if it is a HibernateException;
+ * else returns null to indicate an unknown exception.
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ * @see #convertHibernateAccessException
+ */
+ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+ if (ex instanceof HibernateException) {
+ return convertHibernateAccessException((HibernateException) ex);
+ }
+ return null;
+ }
+
+ /**
+ * Convert the given HibernateException to an appropriate exception from the
+ * org.springframework.dao hierarchy.
+ *
Will automatically apply a specified SQLExceptionTranslator to a
+ * Hibernate JDBCException, else rely on Hibernate's default translation.
+ * @param ex HibernateException that occured
+ * @return a corresponding DataAccessException
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ * @see #setJdbcExceptionTranslator
+ */
+ protected DataAccessException convertHibernateAccessException(HibernateException ex) {
+ if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) {
+ JDBCException jdbcEx = (JDBCException) ex;
+ return this.jdbcExceptionTranslator.translate(
+ "Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
+ }
+ return SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+
+
+ /**
+ * Build the underlying Hibernate SessionFactory.
+ * @return the raw SessionFactory (potentially to be wrapped with a
+ * transaction-aware proxy before it is exposed to the application)
+ * @throws Exception in case of initialization failure
+ */
+ protected abstract SessionFactory buildSessionFactory() throws Exception;
+
+ /**
+ * Hook that allows post-processing after the SessionFactory has been
+ * successfully created. The SessionFactory is already available through
+ * getSessionFactory() at this point.
+ *
This implementation is empty.
+ * @throws Exception in case of initialization failure
+ * @see #getSessionFactory()
+ */
+ protected void afterSessionFactoryCreation() throws Exception {
+ }
+
+ /**
+ * Hook that allows shutdown processing before the SessionFactory
+ * will be closed. The SessionFactory is still available through
+ * getSessionFactory() at this point.
+ *
This implementation is empty. + * @see #getSessionFactory() + */ + protected void beforeSessionFactoryDestruction() { + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java new file mode 100644 index 00000000000..c63c3ac4d93 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.engine.FilterDefinition; +import org.hibernate.type.TypeFactory; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Convenient FactoryBean for defining Hibernate FilterDefinitions. + * Exposes a corresponding Hibernate FilterDefinition object. + * + *
Typically defined as an inner bean within a LocalSessionFactoryBean + * definition, as the list element for the "filterDefinitions" bean property. + * For example: + * + *
+ * <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> + * ... + * <property name="filterDefinitions"> + * <list> + * <bean class="org.springframework.orm.hibernate3.FilterDefinitionFactoryBean"> + * <property name="filterName" value="myFilter"/> + * <property name="parameterTypes"> + * <props> + * <prop key="myParam">string</prop> + * <prop key="myOtherParam">long</prop> + * </props> + * </property> + * </bean> + * </list> + * </property> + * ... + * </bean>+ * + * Alternatively, specify a bean id (or name) attribute for the inner bean, + * instead of the "filterName" property. + * + * @author Juergen Hoeller + * @since 1.2 + * @see org.hibernate.engine.FilterDefinition + * @see LocalSessionFactoryBean#setFilterDefinitions + */ +public class FilterDefinitionFactoryBean implements FactoryBean, BeanNameAware, InitializingBean { + + private String filterName; + + private Map parameterTypeMap = new HashMap(); + + private String defaultFilterCondition; + + private FilterDefinition filterDefinition; + + + /** + * Set the name of the filter. + */ + public void setFilterName(String filterName) { + this.filterName = filterName; + } + + /** + * Set the parameter types for the filter, + * with parameter names as keys and type names as values. + * @see org.hibernate.type.TypeFactory#heuristicType(String) + */ + public void setParameterTypes(Properties parameterTypes) { + if (parameterTypes != null) { + this.parameterTypeMap = new HashMap(parameterTypes.size()); + for (Enumeration names = parameterTypes.propertyNames(); names.hasMoreElements();) { + String paramName = (String) names.nextElement(); + String typeName = parameterTypes.getProperty(paramName); + this.parameterTypeMap.put(paramName, TypeFactory.heuristicType(typeName)); + } + } + else { + this.parameterTypeMap = new HashMap(); + } + } + + /** + * Specify a default filter condition for the filter, if any. + */ + public void setDefaultFilterCondition(String defaultFilterCondition) { + this.defaultFilterCondition = defaultFilterCondition; + } + + /** + * If no explicit filter name has been specified, the bean name of + * the FilterDefinitionFactoryBean will be used. + * @see #setFilterName + */ + public void setBeanName(String name) { + if (this.filterName == null) { + this.filterName = name; + } + } + + public void afterPropertiesSet() { + this.filterDefinition = + new FilterDefinition(this.filterName, this.defaultFilterCondition, this.parameterTypeMap); + } + + + public Object getObject() { + return this.filterDefinition; + } + + public Class getObjectType() { + return FilterDefinition.class; + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java new file mode 100644 index 00000000000..45d2228abbe --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java @@ -0,0 +1,489 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import java.sql.SQLException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Interceptor; +import org.hibernate.JDBCException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.exception.GenericJDBCException; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.Constants; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.support.SQLExceptionTranslator; + +/** + * Base class for {@link HibernateTemplate} and {@link HibernateInterceptor}, + * defining common properties such as SessionFactory and flushing behavior. + * + *
Not intended to be used directly. + * See {@link HibernateTemplate} and {@link HibernateInterceptor}. + * + * @author Juergen Hoeller + * @since 1.2 + * @see HibernateTemplate + * @see HibernateInterceptor + * @see #setFlushMode + */ +public abstract class HibernateAccessor implements InitializingBean, BeanFactoryAware { + + /** + * Never flush is a good strategy for read-only units of work. + * Hibernate will not track and look for changes in this case, + * avoiding any overhead of modification detection. + *
In case of an existing Session, FLUSH_NEVER will turn the flush mode + * to NEVER for the scope of the current operation, resetting the previous + * flush mode afterwards. + * @see #setFlushMode + */ + public static final int FLUSH_NEVER = 0; + + /** + * Automatic flushing is the default mode for a Hibernate Session. + * A session will get flushed on transaction commit, and on certain find + * operations that might involve already modified instances, but not + * after each unit of work like with eager flushing. + *
In case of an existing Session, FLUSH_AUTO will participate in the + * existing flush mode, not modifying it for the current operation. + * This in particular means that this setting will not modify an existing + * flush mode NEVER, in contrast to FLUSH_EAGER. + * @see #setFlushMode + */ + public static final int FLUSH_AUTO = 1; + + /** + * Eager flushing leads to immediate synchronization with the database, + * even if in a transaction. This causes inconsistencies to show up and throw + * a respective exception immediately, and JDBC access code that participates + * in the same transaction will see the changes as the database is already + * aware of them then. But the drawbacks are: + *
In case of an existing Session, FLUSH_EAGER will turn the flush mode + * to AUTO for the scope of the current operation and issue a flush at the + * end, resetting the previous flush mode afterwards. + * @see #setFlushMode + */ + public static final int FLUSH_EAGER = 2; + + /** + * Flushing at commit only is intended for units of work where no + * intermediate flushing is desired, not even for find operations + * that might involve already modified instances. + *
In case of an existing Session, FLUSH_COMMIT will turn the flush mode + * to COMMIT for the scope of the current operation, resetting the previous + * flush mode afterwards. The only exception is an existing flush mode + * NEVER, which will not be modified through this setting. + * @see #setFlushMode + */ + public static final int FLUSH_COMMIT = 3; + + /** + * Flushing before every query statement is rarely necessary. + * It is only available for special needs. + *
In case of an existing Session, FLUSH_ALWAYS will turn the flush mode + * to ALWAYS for the scope of the current operation, resetting the previous + * flush mode afterwards. + * @see #setFlushMode + */ + public static final int FLUSH_ALWAYS = 4; + + + /** Constants instance for HibernateAccessor */ + private static final Constants constants = new Constants(HibernateAccessor.class); + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private SessionFactory sessionFactory; + + private Object entityInterceptor; + + private SQLExceptionTranslator jdbcExceptionTranslator; + + private SQLExceptionTranslator defaultJdbcExceptionTranslator; + + private int flushMode = FLUSH_AUTO; + + private String[] filterNames; + + /** + * Just needed for entityInterceptorBeanName. + * @see #setEntityInterceptorBeanName + */ + private BeanFactory beanFactory; + + + /** + * Set the Hibernate SessionFactory that should be used to create + * Hibernate Sessions. + */ + public void setSessionFactory(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + /** + * Return the Hibernate SessionFactory that should be used to create + * Hibernate Sessions. + */ + public SessionFactory getSessionFactory() { + return this.sessionFactory; + } + + /** + * Set the bean name of a Hibernate entity interceptor that allows to inspect + * and change property values before writing to and reading from the database. + * Will get applied to any new Session created by this transaction manager. + *
Requires the bean factory to be known, to be able to resolve the bean + * name to an interceptor instance on session creation. Typically used for + * prototype interceptors, i.e. a new interceptor instance per session. + *
Can also be used for shared interceptor instances, but it is recommended + * to set the interceptor reference directly in such a scenario. + * @param entityInterceptorBeanName the name of the entity interceptor in + * the bean factory + * @see #setBeanFactory + * @see #setEntityInterceptor + */ + public void setEntityInterceptorBeanName(String entityInterceptorBeanName) { + this.entityInterceptor = entityInterceptorBeanName; + } + + /** + * Set a Hibernate entity interceptor that allows to inspect and change + * property values before writing to and reading from the database. + * Will get applied to any new Session created by this object. + *
Such an interceptor can either be set at the SessionFactory level,
+ * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
+ * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
+ * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
+ * to avoid repeated configuration and guarantee consistent behavior in transactions.
+ * @see #setEntityInterceptorBeanName
+ * @see LocalSessionFactoryBean#setEntityInterceptor
+ * @see HibernateTransactionManager#setEntityInterceptor
+ */
+ public void setEntityInterceptor(Interceptor entityInterceptor) {
+ this.entityInterceptor = entityInterceptor;
+ }
+
+ /**
+ * Return the current Hibernate entity interceptor, or null if none.
+ * Resolves an entity interceptor bean name via the bean factory,
+ * if necessary.
+ * @throws IllegalStateException if bean name specified but no bean factory set
+ * @throws org.springframework.beans.BeansException if bean name resolution via the bean factory failed
+ * @see #setEntityInterceptor
+ * @see #setEntityInterceptorBeanName
+ * @see #setBeanFactory
+ */
+ public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
+ if (this.entityInterceptor instanceof String) {
+ if (this.beanFactory == null) {
+ throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
+ }
+ return (Interceptor) this.beanFactory.getBean((String) this.entityInterceptor, Interceptor.class);
+ }
+ return (Interceptor) this.entityInterceptor;
+ }
+
+ /**
+ * Set the JDBC exception translator for this instance.
+ *
Applied to any SQLException root cause of a Hibernate JDBCException, + * overriding Hibernate's default SQLException translation (which is + * based on Hibernate's Dialect for a specific target database). + * @param jdbcExceptionTranslator the exception translator + * @see java.sql.SQLException + * @see org.hibernate.JDBCException + * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator + * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator + */ + public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { + this.jdbcExceptionTranslator = jdbcExceptionTranslator; + } + + /** + * Return the JDBC exception translator for this instance, if any. + */ + public SQLExceptionTranslator getJdbcExceptionTranslator() { + return this.jdbcExceptionTranslator; + } + + /** + * Set the flush behavior by the name of the respective constant + * in this class, e.g. "FLUSH_AUTO". Default is "FLUSH_AUTO". + * @param constantName name of the constant + * @see #setFlushMode + * @see #FLUSH_AUTO + */ + public void setFlushModeName(String constantName) { + setFlushMode(constants.asNumber(constantName).intValue()); + } + + /** + * Set the flush behavior to one of the constants in this class. + * Default is FLUSH_AUTO. + * @see #setFlushModeName + * @see #FLUSH_AUTO + */ + public void setFlushMode(int flushMode) { + this.flushMode = flushMode; + } + + /** + * Return if a flush should be forced after executing the callback code. + */ + public int getFlushMode() { + return this.flushMode; + } + + /** + * Set the name of a Hibernate filter to be activated for all + * Sessions that this accessor works with. + *
This filter will be enabled at the beginning of each operation + * and correspondingly disabled at the end of the operation. + * This will work for newly opened Sessions as well as for existing + * Sessions (for example, within a transaction). + * @see #enableFilters(org.hibernate.Session) + * @see org.hibernate.Session#enableFilter(String) + * @see LocalSessionFactoryBean#setFilterDefinitions + */ + public void setFilterName(String filter) { + this.filterNames = new String[] {filter}; + } + + /** + * Set one or more names of Hibernate filters to be activated for all + * Sessions that this accessor works with. + *
Each of those filters will be enabled at the beginning of each
+ * operation and correspondingly disabled at the end of the operation.
+ * This will work for newly opened Sessions as well as for existing
+ * Sessions (for example, within a transaction).
+ * @see #enableFilters(org.hibernate.Session)
+ * @see org.hibernate.Session#enableFilter(String)
+ * @see LocalSessionFactoryBean#setFilterDefinitions
+ */
+ public void setFilterNames(String[] filterNames) {
+ this.filterNames = filterNames;
+ }
+
+ /**
+ * Return the names of Hibernate filters to be activated, if any.
+ */
+ public String[] getFilterNames() {
+ return this.filterNames;
+ }
+
+ /**
+ * The bean factory just needs to be known for resolving entity interceptor
+ * bean names. It does not need to be set for any other mode of operation.
+ * @see #setEntityInterceptorBeanName
+ */
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ public void afterPropertiesSet() {
+ if (getSessionFactory() == null) {
+ throw new IllegalArgumentException("Property 'sessionFactory' is required");
+ }
+ }
+
+
+ /**
+ * Apply the flush mode that's been specified for this accessor
+ * to the given Session.
+ * @param session the current Hibernate Session
+ * @param existingTransaction if executing within an existing transaction
+ * @return the previous flush mode to restore after the operation,
+ * or null if none
+ * @see #setFlushMode
+ * @see org.hibernate.Session#setFlushMode
+ */
+ protected FlushMode applyFlushMode(Session session, boolean existingTransaction) {
+ if (getFlushMode() == FLUSH_NEVER) {
+ if (existingTransaction) {
+ FlushMode previousFlushMode = session.getFlushMode();
+ if (!previousFlushMode.lessThan(FlushMode.COMMIT)) {
+ session.setFlushMode(FlushMode.NEVER);
+ return previousFlushMode;
+ }
+ }
+ else {
+ session.setFlushMode(FlushMode.NEVER);
+ }
+ }
+ else if (getFlushMode() == FLUSH_EAGER) {
+ if (existingTransaction) {
+ FlushMode previousFlushMode = session.getFlushMode();
+ if (!previousFlushMode.equals(FlushMode.AUTO)) {
+ session.setFlushMode(FlushMode.AUTO);
+ return previousFlushMode;
+ }
+ }
+ else {
+ // rely on default FlushMode.AUTO
+ }
+ }
+ else if (getFlushMode() == FLUSH_COMMIT) {
+ if (existingTransaction) {
+ FlushMode previousFlushMode = session.getFlushMode();
+ if (previousFlushMode.equals(FlushMode.AUTO) || previousFlushMode.equals(FlushMode.ALWAYS)) {
+ session.setFlushMode(FlushMode.COMMIT);
+ return previousFlushMode;
+ }
+ }
+ else {
+ session.setFlushMode(FlushMode.COMMIT);
+ }
+ }
+ else if (getFlushMode() == FLUSH_ALWAYS) {
+ if (existingTransaction) {
+ FlushMode previousFlushMode = session.getFlushMode();
+ if (!previousFlushMode.equals(FlushMode.ALWAYS)) {
+ session.setFlushMode(FlushMode.ALWAYS);
+ return previousFlushMode;
+ }
+ }
+ else {
+ session.setFlushMode(FlushMode.ALWAYS);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Flush the given Hibernate Session if necessary.
+ * @param session the current Hibernate Session
+ * @param existingTransaction if executing within an existing transaction
+ * @throws HibernateException in case of Hibernate flushing errors
+ */
+ protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException {
+ if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
+ logger.debug("Eagerly flushing Hibernate session");
+ session.flush();
+ }
+ }
+
+
+ /**
+ * Convert the given HibernateException to an appropriate exception
+ * from the org.springframework.dao hierarchy.
+ *
Will automatically apply a specified SQLExceptionTranslator to a
+ * Hibernate JDBCException, else rely on Hibernate's default translation.
+ * @param ex HibernateException that occured
+ * @return a corresponding DataAccessException
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ * @see #setJdbcExceptionTranslator
+ */
+ public DataAccessException convertHibernateAccessException(HibernateException ex) {
+ if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
+ return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
+ }
+ else if (GenericJDBCException.class.equals(ex.getClass())) {
+ return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
+ }
+ return SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+
+ /**
+ * Convert the given Hibernate JDBCException to an appropriate exception
+ * from the org.springframework.dao hierarchy, using the
+ * given SQLExceptionTranslator.
+ * @param ex Hibernate JDBCException that occured
+ * @param translator the SQLExceptionTranslator to use
+ * @return a corresponding DataAccessException
+ */
+ protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
+ return translator.translate("Hibernate operation: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
+ }
+
+ /**
+ * Convert the given SQLException to an appropriate exception from the
+ * org.springframework.dao hierarchy. Can be overridden in subclasses.
+ *
Note that a direct SQLException can just occur when callback code
+ * performs direct JDBC access via Session.connection().
+ * @param ex the SQLException
+ * @return the corresponding DataAccessException instance
+ * @see #setJdbcExceptionTranslator
+ * @see org.hibernate.Session#connection()
+ */
+ protected DataAccessException convertJdbcAccessException(SQLException ex) {
+ SQLExceptionTranslator translator = getJdbcExceptionTranslator();
+ if (translator == null) {
+ translator = getDefaultJdbcExceptionTranslator();
+ }
+ return translator.translate("Hibernate-related JDBC operation", null, ex);
+ }
+
+ /**
+ * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
+ *
Creates a default
+ * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
+ * for the SessionFactory's underlying DataSource.
+ */
+ protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
+ if (this.defaultJdbcExceptionTranslator == null) {
+ this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
+ }
+ return this.defaultJdbcExceptionTranslator;
+ }
+
+
+ /**
+ * Enable the specified filters on the given Session.
+ * @param session the current Hibernate Session
+ * @see #setFilterNames
+ * @see org.hibernate.Session#enableFilter(String)
+ */
+ protected void enableFilters(Session session) {
+ String[] filterNames = getFilterNames();
+ if (filterNames != null) {
+ for (int i = 0; i < filterNames.length; i++) {
+ session.enableFilter(filterNames[i]);
+ }
+ }
+ }
+
+ /**
+ * Disable the specified filters on the given Session.
+ * @param session the current Hibernate Session
+ * @see #setFilterNames
+ * @see org.hibernate.Session#disableFilter(String)
+ */
+ protected void disableFilters(Session session) {
+ String[] filterNames = getFilterNames();
+ if (filterNames != null) {
+ for (int i = 0; i < filterNames.length; i++) {
+ session.disableFilter(filterNames[i]);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java
new file mode 100644
index 00000000000..38846b7d238
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.sql.SQLException;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+
+/**
+ * Callback interface for Hibernate code. To be used with {@link HibernateTemplate}'s
+ * execution methods, often as anonymous classes within a method implementation.
+ * A typical implementation will call Session.load/find/update to perform
+ * some operations on persistent objects. It can also perform direct JDBC operations
+ * via Hibernate's Session.connection(), operating on a JDBC Connection.
+ *
+ *
Note that Hibernate works on unmodified plain Java objects, performing dirty
+ * detection via copies made at load time. Returned objects can thus be used outside
+ * of an active Hibernate Session without any hassle, e.g. for display in a web GUI.
+ * Reassociating such instances with a new Session, e.g. for updates when coming
+ * back from the GUI, is straightforward, as the instance has kept its identity.
+ * You should care to reassociate them as early as possible though, to avoid having
+ * already loaded a version from the database in the same Session.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see HibernateTemplate
+ * @see HibernateTransactionManager
+ */
+public interface HibernateCallback {
+
+ /**
+ * Gets called by HibernateTemplate.execute with an active
+ * Hibernate Session. Does not need to care about activating
+ * or closing the Session, or handling transactions.
+ *
+ *
If called without a thread-bound Hibernate transaction (initiated + * by HibernateTransactionManager), the code will simply get executed on the + * underlying JDBC connection with its transactional semantics. If Hibernate + * is configured to use a JTA-aware DataSource, the JDBC connection and thus + * the callback code will be transactional if a JTA transaction is active. + * + *
Allows for returning a result object created within the callback,
+ * i.e. a domain object or a collection of domain objects.
+ * A thrown custom RuntimeException is treated as an application exception:
+ * It gets propagated to the caller of the template.
+ *
+ * @param session active Hibernate session
+ * @return a result object, or null if none
+ * @throws HibernateException if thrown by the Hibernate API
+ * @throws SQLException if thrown by Hibernate-exposed JDBC API
+ * @see HibernateTemplate#execute
+ * @see HibernateTemplate#executeFind
+ */
+ Object doInHibernate(Session session) throws HibernateException, SQLException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateInterceptor.java
new file mode 100644
index 00000000000..d53bee15402
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateInterceptor.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.hibernate.FlushMode;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * This interceptor binds a new Hibernate Session to the thread before a method
+ * call, closing and removing it afterwards in case of any method outcome.
+ * If there already is a pre-bound Session (e.g. from HibernateTransactionManager,
+ * or from a surrounding Hibernate-intercepted method), the interceptor simply
+ * participates in it.
+ *
+ *
Application code must retrieve a Hibernate Session via the
+ * SessionFactoryUtils.getSession method or - preferably -
+ * Hibernate's own SessionFactory.getCurrentSession() method, to be
+ * able to detect a thread-bound Session. Typically, the code will look like as follows:
+ *
+ *
+ * public void doSomeDataAccessAction() {
+ * Session session = this.sessionFactory.getCurrentSession();
+ * ...
+ * // No need to close the Session or translate exceptions!
+ * }
+ *
+ * Note that this interceptor automatically translates HibernateExceptions,
+ * via delegating to the SessionFactoryUtils.convertHibernateAccessException
+ * method that converts them to exceptions that are compatible with the
+ * org.springframework.dao exception hierarchy (like HibernateTemplate does).
+ * This can be turned off if the raw exceptions are preferred.
+ *
+ * This class can be considered a declarative alternative to HibernateTemplate's + * callback approach. The advantages are: + *
The drawback is the dependency on interceptor configuration. However, note
+ * that this interceptor is usually not necessary in scenarios where the
+ * data access code always executes within transactions. A transaction will always
+ * have a thread-bound Session in the first place, so adding this interceptor to the
+ * configuration just adds value when fine-tuning Session settings like the flush mode
+ * - or when relying on exception translation.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see org.hibernate.SessionFactory#getCurrentSession()
+ * @see HibernateTransactionManager
+ * @see HibernateTemplate
+ */
+public class HibernateInterceptor extends HibernateAccessor implements MethodInterceptor {
+
+ private boolean exceptionConversionEnabled = true;
+
+
+ /**
+ * Set whether to convert any HibernateException raised to a Spring DataAccessException,
+ * compatible with the org.springframework.dao exception hierarchy.
+ *
Default is "true". Turn this flag off to let the caller receive raw exceptions + * as-is, without any wrapping. + * @see org.springframework.dao.DataAccessException + */ + public void setExceptionConversionEnabled(boolean exceptionConversionEnabled) { + this.exceptionConversionEnabled = exceptionConversionEnabled; + } + + + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + Session session = getSession(); + SessionHolder sessionHolder = + (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory()); + + boolean existingTransaction = (sessionHolder != null && sessionHolder.containsSession(session)); + if (existingTransaction) { + logger.debug("Found thread-bound Session for HibernateInterceptor"); + } + else { + if (sessionHolder != null) { + sessionHolder.addSession(session); + } + else { + TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session)); + } + } + + FlushMode previousFlushMode = null; + try { + previousFlushMode = applyFlushMode(session, existingTransaction); + enableFilters(session); + Object retVal = methodInvocation.proceed(); + flushIfNecessary(session, existingTransaction); + return retVal; + } + catch (HibernateException ex) { + if (this.exceptionConversionEnabled) { + throw convertHibernateAccessException(ex); + } + else { + throw ex; + } + } + finally { + if (existingTransaction) { + logger.debug("Not closing pre-bound Hibernate Session after HibernateInterceptor"); + disableFilters(session); + if (previousFlushMode != null) { + session.setFlushMode(previousFlushMode); + } + } + else { + SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory()); + if (sessionHolder == null || sessionHolder.doesNotHoldNonDefaultSession()) { + TransactionSynchronizationManager.unbindResource(getSessionFactory()); + } + } + } + } + + /** + * Return a Session for use by this interceptor. + * @see SessionFactoryUtils#getSession + */ + protected Session getSession() { + return SessionFactoryUtils.getSession( + getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java new file mode 100644 index 00000000000..c6cf41537f0 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import 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 1.2 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateJdbcException extends UncategorizedDataAccessException { + + public HibernateJdbcException(JDBCException ex) { + super("JDBC exception on Hibernate data access: SQLException for SQL [" + ex.getSQL() + "]; SQL state [" + + ex.getSQLState() + "]; error code [" + ex.getErrorCode() + "]; " + ex.getMessage(), ex); + } + + /** + * Return the underlying SQLException. + */ + public SQLException getSQLException() { + return ((JDBCException) getCause()).getSQLException(); + } + + /** + * Return the SQL that led to the problem. + */ + public String getSql() { + return ((JDBCException) getCause()).getSQL(); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java new file mode 100644 index 00000000000..726430889c4 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import 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 1.2 + * @see SessionFactoryUtils#convertHibernateAccessException + */ +public class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException { + + public HibernateObjectRetrievalFailureException(UnresolvableObjectException ex) { + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); + } + + public HibernateObjectRetrievalFailureException(WrongClassException ex) { + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java new file mode 100644 index 00000000000..d162c8fa6ef --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java @@ -0,0 +1,937 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.hibernate.Filter; +import org.hibernate.LockMode; +import org.hibernate.ReplicationMode; +import org.hibernate.criterion.DetachedCriteria; + +import org.springframework.dao.DataAccessException; + +/** + * Interface that specifies a basic set of Hibernate operations, + * implemented by {@link HibernateTemplate}. Not often used, but a useful + * option to enhance testability, as it can easily be mocked or stubbed. + * + *
Defines HibernateTemplate's data access methods that
+ * mirror various {@link org.hibernate.Session} methods. Users are
+ * strongly encouraged to read the Hibernate Session javadocs
+ * for details on the semantics of those methods.
+ *
+ *
Note that operations that return an {@link java.util.Iterator} (i.e.
+ * iterate(..)) are supposed to be used within Spring-driven
+ * or JTA-driven transactions (with {@link HibernateTransactionManager},
+ * {@link org.springframework.transaction.jta.JtaTransactionManager},
+ * or EJB CMT). Else, the Iterator won't be able to read
+ * results from its {@link java.sql.ResultSet} anymore, as the underlying
+ * Hibernate Session will already have been closed.
+ *
+ *
Note that lazy loading will just work with an open Hibernate
+ * Session, either within a transaction or within
+ * {@link org.springframework.orm.hibernate3.support.OpenSessionInViewFilter}/
+ * {@link org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor}.
+ * Furthermore, some operations just make sense within transactions,
+ * for example: contains, evict, lock,
+ * flush, clear.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see HibernateTemplate
+ * @see org.hibernate.Session
+ * @see HibernateTransactionManager
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
+ */
+public interface HibernateOperations {
+
+ /**
+ * Execute the action specified by the given action object within a
+ * {@link org.hibernate.Session}.
+ *
Application exceptions thrown by the action object get propagated + * to the caller (can only be unchecked). Hibernate exceptions are + * transformed into appropriate DAO ones. Allows for returning a result + * object, that is a domain object or a collection of domain objects. + *
Note: Callback code is not supposed to handle transactions itself!
+ * Use an appropriate transaction manager like
+ * {@link HibernateTransactionManager}. Generally, callback code must not
+ * touch any Session lifecycle methods, like close,
+ * disconnect, or reconnect, to let the template do its work.
+ * @param action callback object that specifies the Hibernate action
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see HibernateTransactionManager
+ * @see org.springframework.dao
+ * @see org.springframework.transaction
+ * @see org.hibernate.Session
+ */
+ Object execute(HibernateCallback action) throws DataAccessException;
+
+ /**
+ * Execute the specified action assuming that the result object is a
+ * {@link List}.
+ *
This is a convenience method for executing Hibernate find calls or
+ * queries within an action.
+ * @param action calback object that specifies the Hibernate action
+ * @return a List result returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ */
+ List executeFind(HibernateCallback action) throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for loading individual objects
+ //-------------------------------------------------------------------------
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, or null if not found.
+ *
This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(Class, java.io.Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityClass a persistent class
+ * @param id the identifier of the persistent instance
+ * @return the persistent instance, or null if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, java.io.Serializable)
+ */
+ Object get(Class entityClass, Serializable id) throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, or null if not found.
+ *
Obtains the specified lock mode if the instance exists. + *
This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(Class, java.io.Serializable, LockMode)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityClass a persistent class
+ * @param id the identifier of the persistent instance
+ * @param lockMode the lock mode to obtain
+ * @return the persistent instance, or null if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, java.io.Serializable, org.hibernate.LockMode)
+ */
+ Object get(Class entityClass, Serializable id, LockMode lockMode)
+ throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, or null if not found.
+ *
This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(String, java.io.Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityName the name of the persistent entity
+ * @param id the identifier of the persistent instance
+ * @return the persistent instance, or null if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, java.io.Serializable)
+ */
+ Object get(String entityName, Serializable id) throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, or null if not found.
+ * Obtains the specified lock mode if the instance exists.
+ *
This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(String, java.io.Serializable, LockMode)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityName the name of the persistent entity
+ * @param id the identifier of the persistent instance
+ * @param lockMode the lock mode to obtain
+ * @return the persistent instance, or null if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, java.io.Serializable, org.hibernate.LockMode)
+ */
+ Object get(String entityName, Serializable id, LockMode lockMode)
+ throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, throwing an exception if not found.
+ *
This method is a thin wrapper around + * {@link org.hibernate.Session#load(Class, java.io.Serializable)} for convenience. + * For an explanation of the exact semantics of this method, please do refer to + * the Hibernate API documentation in the first instance. + * @param entityClass a persistent class + * @param id the identifier of the persistent instance + * @return the persistent instance + * @throws org.springframework.orm.ObjectRetrievalFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#load(Class, java.io.Serializable) + */ + Object load(Class entityClass, Serializable id) throws DataAccessException; + + /** + * Return the persistent instance of the given entity class + * with the given identifier, throwing an exception if not found. + * Obtains the specified lock mode if the instance exists. + *
This method is a thin wrapper around + * {@link org.hibernate.Session#load(Class, java.io.Serializable, LockMode)} for convenience. + * For an explanation of the exact semantics of this method, please do refer to + * the Hibernate API documentation in the first instance. + * @param entityClass a persistent class + * @param id the identifier of the persistent instance + * @param lockMode the lock mode to obtain + * @return the persistent instance + * @throws org.springframework.orm.ObjectRetrievalFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#load(Class, java.io.Serializable) + */ + Object load(Class entityClass, Serializable id, LockMode lockMode) + throws DataAccessException; + + /** + * Return the persistent instance of the given entity class + * with the given identifier, throwing an exception if not found. + *
This method is a thin wrapper around + * {@link org.hibernate.Session#load(String, java.io.Serializable)} for convenience. + * For an explanation of the exact semantics of this method, please do refer to + * the Hibernate API documentation in the first instance. + * @param entityName the name of the persistent entity + * @param id the identifier of the persistent instance + * @return the persistent instance + * @throws org.springframework.orm.ObjectRetrievalFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#load(Class, java.io.Serializable) + */ + Object load(String entityName, Serializable id) throws DataAccessException; + + /** + * Return the persistent instance of the given entity class + * with the given identifier, throwing an exception if not found. + *
Obtains the specified lock mode if the instance exists. + *
This method is a thin wrapper around + * {@link org.hibernate.Session#load(String, java.io.Serializable, LockMode)} for convenience. + * For an explanation of the exact semantics of this method, please do refer to + * the Hibernate API documentation in the first instance. + * @param entityName the name of the persistent entity + * @param id the identifier of the persistent instance + * @param lockMode the lock mode to obtain + * @return the persistent instance + * @throws org.springframework.orm.ObjectRetrievalFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#load(Class, java.io.Serializable) + */ + Object load(String entityName, Serializable id, LockMode lockMode) + throws DataAccessException; + + /** + * Return all persistent instances of the given entity class. + * Note: Use queries or criteria for retrieving a specific subset. + * @param entityClass a persistent class + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException if there is a Hibernate error + * @see org.hibernate.Session#createCriteria + */ + List loadAll(Class entityClass) throws DataAccessException; + + /** + * Load the persistent instance with the given identifier + * into the given object, throwing an exception if not found. + *
This method is a thin wrapper around
+ * {@link org.hibernate.Session#load(Object, java.io.Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entity the object (of the target class) to load into
+ * @param id the identifier of the persistent instance
+ * @throws org.springframework.orm.ObjectRetrievalFailureException if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#load(Object, java.io.Serializable)
+ */
+ void load(Object entity, Serializable id) throws DataAccessException;
+
+ /**
+ * Re-read the state of the given persistent instance.
+ * @param entity the persistent instance to re-read
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#refresh(Object)
+ */
+ void refresh(Object entity) throws DataAccessException;
+
+ /**
+ * Re-read the state of the given persistent instance.
+ * Obtains the specified lock mode for the instance.
+ * @param entity the persistent instance to re-read
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#refresh(Object, org.hibernate.LockMode)
+ */
+ void refresh(Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Check whether the given object is in the Session cache.
+ * @param entity the persistence instance to check
+ * @return whether the given object is in the Session cache
+ * @throws org.springframework.dao.DataAccessException if there is a Hibernate error
+ * @see org.hibernate.Session#contains
+ */
+ boolean contains(Object entity) throws DataAccessException;
+
+ /**
+ * Remove the given object from the {@link org.hibernate.Session} cache.
+ * @param entity the persistent instance to evict
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#evict
+ */
+ void evict(Object entity) throws DataAccessException;
+
+ /**
+ * Force initialization of a Hibernate proxy or persistent collection.
+ * @param proxy a proxy for a persistent object or a persistent collection
+ * @throws DataAccessException if we can't initialize the proxy, for example
+ * because it is not associated with an active Session
+ * @see org.hibernate.Hibernate#initialize
+ */
+ void initialize(Object proxy) throws DataAccessException;
+
+ /**
+ * Return an enabled Hibernate {@link Filter} for the given filter name.
+ * The returned Filter instance can be used to set filter parameters.
+ * @param filterName the name of the filter
+ * @return the enabled Hibernate Filter (either already
+ * enabled or enabled on the fly by this operation)
+ * @throws IllegalStateException if we are not running within a
+ * transactional Session (in which case this operation does not make sense)
+ */
+ Filter enableFilter(String filterName) throws IllegalStateException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for storing individual objects
+ //-------------------------------------------------------------------------
+
+ /**
+ * Obtain the specified lock level upon the given object, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entity the persistent instance to lock
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#lock(Object, org.hibernate.LockMode)
+ */
+ void lock(Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Obtain the specified lock level upon the given object, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to lock
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#lock(String, Object, org.hibernate.LockMode)
+ */
+ void lock(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance.
+ * @param entity the transient instance to persist
+ * @return the generated identifier
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#save(Object)
+ */
+ Serializable save(Object entity) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance.
+ * @param entityName the name of the persistent entity
+ * @param entity the transient instance to persist
+ * @return the generated identifier
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#save(String, Object)
+ */
+ Serializable save(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Update the given persistent instance,
+ * associating it with the current Hibernate {@link org.hibernate.Session}.
+ * @param entity the persistent instance to update
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#update(Object)
+ */
+ void update(Object entity) throws DataAccessException;
+
+ /**
+ * Update the given persistent instance,
+ * associating it with the current Hibernate {@link org.hibernate.Session}.
+ *
Obtains the specified lock mode if the instance exists, implicitly + * checking whether the corresponding database entry still exists. + * @param entity the persistent instance to update + * @param lockMode the lock mode to obtain + * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#update(Object) + */ + void update(Object entity, LockMode lockMode) throws DataAccessException; + + /** + * Update the given persistent instance, + * associating it with the current Hibernate {@link org.hibernate.Session}. + * @param entityName the name of the persistent entity + * @param entity the persistent instance to update + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#update(String, Object) + */ + void update(String entityName, Object entity) throws DataAccessException; + + /** + * Update the given persistent instance, + * associating it with the current Hibernate {@link org.hibernate.Session}. + *
Obtains the specified lock mode if the instance exists, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to update
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#update(String, Object)
+ */
+ void update(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Save or update the given persistent instance,
+ * according to its id (matching the configured "unsaved-value"?).
+ * Associates the instance with the current Hibernate {@link org.hibernate.Session}.
+ * @param entity the persistent instance to save or update
+ * (to be associated with the Hibernate Session)
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#saveOrUpdate(Object)
+ */
+ void saveOrUpdate(Object entity) throws DataAccessException;
+
+ /**
+ * Save or update the given persistent instance,
+ * according to its id (matching the configured "unsaved-value"?).
+ * Associates the instance with the current Hibernate Session.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to save or update
+ * (to be associated with the Hibernate Session)
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#saveOrUpdate(String, Object)
+ */
+ void saveOrUpdate(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Save or update all given persistent instances,
+ * according to its id (matching the configured "unsaved-value"?).
+ * Associates the instances with the current Hibernate Session.
+ * @param entities the persistent instances to save or update
+ * (to be associated with the Hibernate Session)
+ * @throws DataAccessException in case of Hibernate errors
+ * @deprecated as of Spring 2.5, in favor of individual
+ * saveOrUpdate or merge usage
+ */
+ void saveOrUpdateAll(Collection entities) throws DataAccessException;
+
+ /**
+ * Persist the state of the given detached instance according to the
+ * given replication mode, reusing the current identifier value.
+ * @param entity the persistent object to replicate
+ * @param replicationMode the Hibernate ReplicationMode
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#replicate(Object, org.hibernate.ReplicationMode)
+ */
+ void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException;
+
+ /**
+ * Persist the state of the given detached instance according to the
+ * given replication mode, reusing the current identifier value.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent object to replicate
+ * @param replicationMode the Hibernate ReplicationMode
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#replicate(String, Object, org.hibernate.ReplicationMode)
+ */
+ void replicate(String entityName, Object entity, ReplicationMode replicationMode) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance. Follows JSR-220 semantics.
+ *
Similar to save, associating the given object
+ * with the current Hibernate {@link org.hibernate.Session}.
+ * @param entity the persistent instance to persist
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#persist(Object)
+ * @see #save
+ */
+ void persist(Object entity) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance. Follows JSR-220 semantics.
+ *
Similar to save, associating the given object
+ * with the current Hibernate {@link org.hibernate.Session}.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to persist
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#persist(String, Object)
+ * @see #save
+ */
+ void persist(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Copy the state of the given object onto the persistent object
+ * with the same identifier. Follows JSR-220 semantics.
+ *
Similar to saveOrUpdate, but never associates the given
+ * object with the current Hibernate Session. In case of a new entity,
+ * the state will be copied over as well.
+ *
Note that merge will not update the identifiers
+ * in the passed-in object graph (in contrast to TopLink)! Consider
+ * registering Spring's IdTransferringMergeEventListener if
+ * you would like to have newly assigned ids transferred to the original
+ * object graph too.
+ * @param entity the object to merge with the corresponding persistence instance
+ * @return the updated, registered persistent instance
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#merge(Object)
+ * @see #saveOrUpdate
+ * @see org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener
+ */
+ Object merge(Object entity) throws DataAccessException;
+
+ /**
+ * Copy the state of the given object onto the persistent object
+ * with the same identifier. Follows JSR-220 semantics.
+ *
Similar to saveOrUpdate, but never associates the given
+ * object with the current Hibernate {@link org.hibernate.Session}. In
+ * the case of a new entity, the state will be copied over as well.
+ *
Note that merge will not update the identifiers
+ * in the passed-in object graph (in contrast to TopLink)! Consider
+ * registering Spring's IdTransferringMergeEventListener
+ * if you would like to have newly assigned ids transferred to the
+ * original object graph too.
+ * @param entityName the name of the persistent entity
+ * @param entity the object to merge with the corresponding persistence instance
+ * @return the updated, registered persistent instance
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#merge(String, Object)
+ * @see #saveOrUpdate
+ */
+ Object merge(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Delete the given persistent instance.
+ * @param entity the persistent instance to delete
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#delete(Object)
+ */
+ void delete(Object entity) throws DataAccessException;
+
+ /**
+ * Delete the given persistent instance.
+ *
Obtains the specified lock mode if the instance exists, implicitly + * checking whether the corresponding database entry still exists. + * @param entity the persistent instance to delete + * @param lockMode the lock mode to obtain + * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#delete(Object) + */ + void delete(Object entity, LockMode lockMode) throws DataAccessException; + + /** + * Delete the given persistent instance. + * @param entityName the name of the persistent entity + * @param entity the persistent instance to delete + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#delete(Object) + */ + void delete(String entityName, Object entity) throws DataAccessException; + + /** + * Delete the given persistent instance. + *
Obtains the specified lock mode if the instance exists, implicitly + * checking whether the corresponding database entry still exists. + * @param entityName the name of the persistent entity + * @param entity the persistent instance to delete + * @param lockMode the lock mode to obtain + * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#delete(Object) + */ + void delete(String entityName, Object entity, LockMode lockMode) throws DataAccessException; + + /** + * Delete all given persistent instances. + *
This can be combined with any of the find methods to delete by query + * in two lines of code. + * @param entities the persistent instances to delete + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#delete(Object) + */ + void deleteAll(Collection entities) throws DataAccessException; + + /** + * Flush all pending saves, updates and deletes to the database. + *
Only invoke this for selective eager flushing, for example when + * JDBC code needs to see certain changes within the same transaction. + * Else, it is preferable to rely on auto-flushing at transaction + * completion. + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#flush + */ + void flush() throws DataAccessException; + + /** + * Remove all objects from the {@link org.hibernate.Session} cache, and + * cancel all pending saves, updates and deletes. + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#clear + */ + void clear() throws DataAccessException; + + + //------------------------------------------------------------------------- + // Convenience finder methods for HQL strings + //------------------------------------------------------------------------- + + /** + * Execute an HQL query. + * @param queryString a query expressed in Hibernate's query language + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#createQuery + */ + List find(String queryString) throws DataAccessException; + + /** + * Execute an HQL query, binding one value to a "?" parameter in the + * query string. + * @param queryString a query expressed in Hibernate's query language + * @param value the value of the parameter + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#createQuery + */ + List find(String queryString, Object value) throws DataAccessException; + + /** + * Execute an HQL query, binding a number of values to "?" parameters + * in the query string. + * @param queryString a query expressed in Hibernate's query language + * @param values the values of the parameters + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#createQuery + */ + List find(String queryString, Object[] values) throws DataAccessException; + + /** + * Execute an HQL query, binding one value to a ":" named parameter + * in the query string. + * @param queryString a query expressed in Hibernate's query language + * @param paramName the name of the parameter + * @param value the value of the parameter + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedParam(String queryString, String paramName, Object value) + throws DataAccessException; + + /** + * Execute an HQL query, binding a number of values to ":" named + * parameters in the query string. + * @param queryString a query expressed in Hibernate's query language + * @param paramNames the names of the parameters + * @param values the values of the parameters + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedParam(String queryString, String[] paramNames, Object[] values) + throws DataAccessException; + + /** + * Execute an HQL query, binding the properties of the given bean to + * named parameters in the query string. + * @param queryString a query expressed in Hibernate's query language + * @param valueBean the values of the parameters + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Query#setProperties + * @see org.hibernate.Session#createQuery + */ + List findByValueBean(String queryString, Object valueBean) throws DataAccessException; + + + //------------------------------------------------------------------------- + // Convenience finder methods for named queries + //------------------------------------------------------------------------- + + /** + * Execute a named query. + *
A named query is defined in a Hibernate mapping file. + * @param queryName the name of a Hibernate query in a mapping file + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedQuery(String queryName) throws DataAccessException; + + /** + * Execute a named query, binding one value to a "?" parameter in + * the query string. + *
A named query is defined in a Hibernate mapping file. + * @param queryName the name of a Hibernate query in a mapping file + * @param value the value of the parameter + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedQuery(String queryName, Object value) throws DataAccessException; + + /** + * Execute a named query binding a number of values to "?" parameters + * in the query string. + *
A named query is defined in a Hibernate mapping file. + * @param queryName the name of a Hibernate query in a mapping file + * @param values the values of the parameters + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedQuery(String queryName, Object[] values) throws DataAccessException; + + /** + * Execute a named query, binding one value to a ":" named parameter + * in the query string. + *
A named query is defined in a Hibernate mapping file. + * @param queryName the name of a Hibernate query in a mapping file + * @param paramName the name of parameter + * @param value the value of the parameter + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value) + throws DataAccessException; + + /** + * Execute a named query, binding a number of values to ":" named + * parameters in the query string. + *
A named query is defined in a Hibernate mapping file. + * @param queryName the name of a Hibernate query in a mapping file + * @param paramNames the names of the parameters + * @param values the values of the parameters + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedQueryAndNamedParam(String queryName, String[] paramNames, Object[] values) + throws DataAccessException; + + /** + * Execute a named query, binding the properties of the given bean to + * ":" named parameters in the query string. + *
A named query is defined in a Hibernate mapping file. + * @param queryName the name of a Hibernate query in a mapping file + * @param valueBean the values of the parameters + * @return a {@link List} containing the results of the query execution + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Query#setProperties + * @see org.hibernate.Session#getNamedQuery(String) + */ + List findByNamedQueryAndValueBean(String queryName, Object valueBean) + throws DataAccessException; + + + //------------------------------------------------------------------------- + // Convenience finder methods for detached criteria + //------------------------------------------------------------------------- + + /** + * Execute a query based on a given Hibernate criteria object. + * @param criteria the detached Hibernate criteria object. + * Note: Do not reuse criteria objects! They need to recreated per execution, + * due to the suboptimal design of Hibernate's criteria facility. + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.criterion.DetachedCriteria#getExecutableCriteria(org.hibernate.Session) + */ + List findByCriteria(DetachedCriteria criteria) throws DataAccessException; + + /** + * Execute a query based on the given Hibernate criteria object. + * @param criteria the detached Hibernate criteria object. + * Note: Do not reuse criteria objects! They need to recreated per execution, + * due to the suboptimal design of Hibernate's criteria facility. + * @param firstResult the index of the first result object to be retrieved + * (numbered from 0) + * @param maxResults the maximum number of result objects to retrieve + * (or <=0 for no limit) + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.criterion.DetachedCriteria#getExecutableCriteria(org.hibernate.Session) + * @see org.hibernate.Criteria#setFirstResult(int) + * @see org.hibernate.Criteria#setMaxResults(int) + */ + List findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults) throws DataAccessException; + + /** + * Execute a query based on the given example entity object. + * @param exampleEntity an instance of the desired entity, + * serving as example for "query-by-example" + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.criterion.Example#create(Object) + */ + List findByExample(Object exampleEntity) throws DataAccessException; + + /** + * Execute a query based on the given example entity object. + * @param entityName the name of the persistent entity + * @param exampleEntity an instance of the desired entity, + * serving as example for "query-by-example" + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.criterion.Example#create(Object) + */ + List findByExample(String entityName, Object exampleEntity) throws DataAccessException; + + /** + * Execute a query based on a given example entity object. + * @param exampleEntity an instance of the desired entity, + * serving as example for "query-by-example" + * @param firstResult the index of the first result object to be retrieved + * (numbered from 0) + * @param maxResults the maximum number of result objects to retrieve + * (or <=0 for no limit) + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.criterion.Example#create(Object) + * @see org.hibernate.Criteria#setFirstResult(int) + * @see org.hibernate.Criteria#setMaxResults(int) + */ + List findByExample(Object exampleEntity, int firstResult, int maxResults) throws DataAccessException; + + /** + * Execute a query based on a given example entity object. + * @param entityName the name of the persistent entity + * @param exampleEntity an instance of the desired entity, + * serving as example for "query-by-example" + * @param firstResult the index of the first result object to be retrieved + * (numbered from 0) + * @param maxResults the maximum number of result objects to retrieve + * (or <=0 for no limit) + * @return a {@link List} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.criterion.Example#create(Object) + * @see org.hibernate.Criteria#setFirstResult(int) + * @see org.hibernate.Criteria#setMaxResults(int) + */ + List findByExample(String entityName, Object exampleEntity, int firstResult, int maxResults) + throws DataAccessException; + + + //------------------------------------------------------------------------- + // Convenience query methods for iteration and bulk updates/deletes + //------------------------------------------------------------------------- + + /** + * Execute a query for persistent instances. + *
Returns the results as an {@link Iterator}. Entities returned are + * initialized on demand. See the Hibernate API documentation for details. + * @param queryString a query expressed in Hibernate's query language + * @return an {@link Iterator} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#createQuery + * @see org.hibernate.Query#iterate + */ + Iterator iterate(String queryString) throws DataAccessException; + + /** + * Execute a query for persistent instances, binding one value + * to a "?" parameter in the query string. + *
Returns the results as an {@link Iterator}. Entities returned are + * initialized on demand. See the Hibernate API documentation for details. + * @param queryString a query expressed in Hibernate's query language + * @param value the value of the parameter + * @return an {@link Iterator} containing 0 or more persistent instances + * @throws org.springframework.dao.DataAccessException in case of Hibernate errors + * @see org.hibernate.Session#createQuery + * @see org.hibernate.Query#iterate + */ + Iterator iterate(String queryString, Object value) throws DataAccessException; + + /** + * Execute a query for persistent instances, binding a number of + * values to "?" parameters in the query string. + *
Returns the results as an {@link Iterator}. Entities returned are
+ * initialized on demand. See the Hibernate API documentation for details.
+ * @param queryString a query expressed in Hibernate's query language
+ * @param values the values of the parameters
+ * @return an {@link Iterator} containing 0 or more persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#createQuery
+ * @see org.hibernate.Query#iterate
+ */
+ Iterator iterate(String queryString, Object[] values) throws DataAccessException;
+
+ /**
+ * Immediately close an {@link Iterator} created by any of the various
+ * iterate(..) operations, instead of waiting until the
+ * session is closed or disconnected.
+ * @param it the Iterator to close
+ * @throws DataAccessException if the Iterator could not be closed
+ * @see org.hibernate.Hibernate#close
+ */
+ void closeIterator(Iterator it) throws DataAccessException;
+
+ /**
+ * Update/delete all objects according to the given query.
+ * @param queryString an update/delete query expressed in Hibernate's query language
+ * @return the number of instances updated/deleted
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#createQuery
+ * @see org.hibernate.Query#executeUpdate
+ */
+ int bulkUpdate(String queryString) throws DataAccessException;
+
+ /**
+ * Update/delete all objects according to the given query, binding one value
+ * to a "?" parameter in the query string.
+ * @param queryString an update/delete query expressed in Hibernate's query language
+ * @param value the value of the parameter
+ * @return the number of instances updated/deleted
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#createQuery
+ * @see org.hibernate.Query#executeUpdate
+ */
+ int bulkUpdate(String queryString, Object value) throws DataAccessException;
+
+ /**
+ * Update/delete all objects according to the given query, binding a number of
+ * values to "?" parameters in the query string.
+ * @param queryString an update/delete query expressed in Hibernate's query language
+ * @param values the values of the parameters
+ * @return the number of instances updated/deleted
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#createQuery
+ * @see org.hibernate.Query#executeUpdate
+ */
+ int bulkUpdate(String queryString, Object[] values) throws DataAccessException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java
new file mode 100644
index 00000000000..fb58c8efcc8
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import 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 1.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+public class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
+
+ public HibernateOptimisticLockingFailureException(StaleObjectStateException ex) {
+ super(ex.getEntityName(), ex.getIdentifier(), ex);
+ }
+
+ public HibernateOptimisticLockingFailureException(StaleStateException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java
new file mode 100644
index 00000000000..743600991b6
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import org.hibernate.QueryException;
+
+import org.springframework.dao.InvalidDataAccessResourceUsageException;
+
+/**
+ * Hibernate-specific subclass of InvalidDataAccessResourceUsageException,
+ * thrown on invalid HQL query syntax.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+public class HibernateQueryException extends InvalidDataAccessResourceUsageException {
+
+ public HibernateQueryException(QueryException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+ /**
+ * Return the HQL query string that was invalid.
+ */
+ public String getQueryString() {
+ return ((QueryException) getCause()).getQueryString();
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java
new file mode 100644
index 00000000000..83fdb0f3d51
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import org.hibernate.HibernateException;
+
+import org.springframework.dao.UncategorizedDataAccessException;
+
+/**
+ * Hibernate-specific subclass of UncategorizedDataAccessException,
+ * for Hibernate system errors that do not match any concrete
+ * org.springframework.dao exceptions.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+public class HibernateSystemException extends UncategorizedDataAccessException {
+
+ /**
+ * Create a new HibernateSystemException,
+ * wrapping an arbitrary HibernateException.
+ * @param cause the HibernateException thrown
+ */
+ public HibernateSystemException(HibernateException cause) {
+ super(cause != null ? cause.getMessage() : null, cause);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java
new file mode 100644
index 00000000000..86515061e17
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java
@@ -0,0 +1,1312 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.hibernate.Criteria;
+import org.hibernate.Filter;
+import org.hibernate.FlushMode;
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
+import org.hibernate.Query;
+import org.hibernate.ReplicationMode;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.criterion.DetachedCriteria;
+import org.hibernate.criterion.Example;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.event.EventSource;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class that simplifies Hibernate data access code. Automatically
+ * converts HibernateExceptions into DataAccessExceptions, following the
+ * org.springframework.dao exception hierarchy.
+ *
+ *
The central method is execute, supporting Hibernate access code
+ * implementing the {@link HibernateCallback} interface. It provides Hibernate Session
+ * handling such that neither the HibernateCallback implementation nor the calling
+ * code needs to explicitly care about retrieving/closing Hibernate Sessions,
+ * or handling Session lifecycle exceptions. For typical single step actions,
+ * there are various convenience methods (find, load, saveOrUpdate, delete).
+ *
+ *
Can be used within a service implementation via direct instantiation + * with a SessionFactory reference, or get prepared in an application context + * and given to services as bean reference. Note: The SessionFactory should + * always be configured as bean in the application context, in the first case + * given to the service directly, in the second case to the prepared template. + * + *
NOTE: As of Hibernate 3.0.1, transactional Hibernate access code can + * also be coded in plain Hibernate style. Hence, for newly started projects, + * consider adopting the standard Hibernate3 style of coding data access objects + * instead, based on {@link org.hibernate.SessionFactory#getCurrentSession()}. + * + *
This class can be considered as direct alternative to working with the raw
+ * Hibernate3 Session API (through SessionFactory.getCurrentSession()).
+ * The major advantage is its automatic conversion to DataAccessExceptions as well
+ * as its capability to fall back to 'auto-commit' style behavior when used outside
+ * of transactions. Note that HibernateTemplate will perform its own Session
+ * management, not participating in a custom Hibernate CurrentSessionContext
+ * unless you explicitly switch {@link #setAllowCreate "allowCreate"} to "false".
+ *
+ *
{@link LocalSessionFactoryBean} is the preferred way of obtaining a reference + * to a specific Hibernate SessionFactory, at least in a non-EJB environment. + * The Spring application context will manage its lifecycle, initializing and + * shutting down the factory as part of the application. + * + *
Note that operations that return an Iterator (i.e. iterate)
+ * are supposed to be used within Spring-driven or JTA-driven transactions
+ * (with HibernateTransactionManager, JtaTransactionManager, or EJB CMT).
+ * Else, the Iterator won't be able to read results from its ResultSet anymore,
+ * as the underlying Hibernate Session will already have been closed.
+ *
+ *
Lazy loading will also just work with an open Hibernate Session,
+ * either within a transaction or within OpenSessionInViewFilter/Interceptor.
+ * Furthermore, some operations just make sense within transactions,
+ * for example: contains, evict, lock,
+ * flush, clear.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #setSessionFactory
+ * @see HibernateCallback
+ * @see org.hibernate.Session
+ * @see LocalSessionFactoryBean
+ * @see HibernateTransactionManager
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
+ */
+public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {
+
+ private boolean allowCreate = true;
+
+ private boolean alwaysUseNewSession = false;
+
+ private boolean exposeNativeSession = false;
+
+ private boolean checkWriteOperations = true;
+
+ private boolean cacheQueries = false;
+
+ private String queryCacheRegion;
+
+ private int fetchSize = 0;
+
+ private int maxResults = 0;
+
+
+ /**
+ * Create a new HibernateTemplate instance.
+ */
+ public HibernateTemplate() {
+ }
+
+ /**
+ * Create a new HibernateTemplate instance.
+ * @param sessionFactory SessionFactory to create Sessions
+ */
+ public HibernateTemplate(SessionFactory sessionFactory) {
+ setSessionFactory(sessionFactory);
+ afterPropertiesSet();
+ }
+
+ /**
+ * Create a new HibernateTemplate instance.
+ * @param sessionFactory SessionFactory to create Sessions
+ * @param allowCreate if a non-transactional Session should be created when no
+ * transactional Session can be found for the current thread
+ */
+ public HibernateTemplate(SessionFactory sessionFactory, boolean allowCreate) {
+ setSessionFactory(sessionFactory);
+ setAllowCreate(allowCreate);
+ afterPropertiesSet();
+ }
+
+
+ /**
+ * Set if a new {@link Session} should be created when no transactional
+ * Session can be found for the current thread.
+ * The default value is true.
+ *
HibernateTemplate is aware of a corresponding
+ * Session bound to the current thread, for example when using
+ * {@link HibernateTransactionManager}. If allowCreate is
+ * true, a new non-transactional Session will be
+ * created if none is found, which needs to be closed at the end of the operation.
+ * If false, an {@link IllegalStateException} will get thrown in
+ * this case.
+ *
NOTE: As of Spring 2.5, switching allowCreate
+ * to false will delegate to Hibernate's
+ * {@link org.hibernate.SessionFactory#getCurrentSession()} method,
+ * which - with Spring-based setup - will by default delegate to Spring's
+ * SessionFactoryUtils.getSession(sessionFactory, false).
+ * This mode also allows for custom Hibernate CurrentSessionContext strategies
+ * to be plugged in, whereas allowCreate set to true
+ * will always use a Spring-managed Hibernate Session.
+ * @see SessionFactoryUtils#getSession(SessionFactory, boolean)
+ */
+ public void setAllowCreate(boolean allowCreate) {
+ this.allowCreate = allowCreate;
+ }
+
+ /**
+ * Return if a new Session should be created if no thread-bound found.
+ */
+ public boolean isAllowCreate() {
+ return this.allowCreate;
+ }
+
+ /**
+ * Set whether to always use a new Hibernate Session for this template.
+ * Default is "false"; if activated, all operations on this template will
+ * work on a new Hibernate Session even in case of a pre-bound Session
+ * (for example, within a transaction or OpenSessionInViewFilter).
+ *
Within a transaction, a new Hibernate Session used by this template + * will participate in the transaction through using the same JDBC + * Connection. In such a scenario, multiple Sessions will participate + * in the same database transaction. + *
Turn this on for operations that are supposed to always execute + * independently, without side effects caused by a shared Hibernate Session. + */ + public void setAlwaysUseNewSession(boolean alwaysUseNewSession) { + this.alwaysUseNewSession = alwaysUseNewSession; + } + + /** + * Return whether to always use a new Hibernate Session for this template. + */ + public boolean isAlwaysUseNewSession() { + return this.alwaysUseNewSession; + } + + /** + * Set whether to expose the native Hibernate Session to + * HibernateCallback code. + *
Default is "false": a Session proxy will be returned, suppressing
+ * close calls and automatically applying query cache
+ * settings and transaction timeouts.
+ * @see HibernateCallback
+ * @see org.hibernate.Session
+ * @see #setCacheQueries
+ * @see #setQueryCacheRegion
+ * @see #prepareQuery
+ * @see #prepareCriteria
+ */
+ public void setExposeNativeSession(boolean exposeNativeSession) {
+ this.exposeNativeSession = exposeNativeSession;
+ }
+
+ /**
+ * Return whether to expose the native Hibernate Session to
+ * HibernateCallback code, or rather a Session proxy.
+ */
+ public boolean isExposeNativeSession() {
+ return this.exposeNativeSession;
+ }
+
+ /**
+ * Set whether to check that the Hibernate Session is not in read-only mode
+ * in case of write operations (save/update/delete).
+ *
Default is "true", for fail-fast behavior when attempting write operations + * within a read-only transaction. Turn this off to allow save/update/delete + * on a Session with flush mode NEVER. + * @see #setFlushMode + * @see #checkWriteOperationAllowed + * @see org.springframework.transaction.TransactionDefinition#isReadOnly + */ + public void setCheckWriteOperations(boolean checkWriteOperations) { + this.checkWriteOperations = checkWriteOperations; + } + + /** + * Return whether to check that the Hibernate Session is not in read-only + * mode in case of write operations (save/update/delete). + */ + public boolean isCheckWriteOperations() { + return this.checkWriteOperations; + } + + /** + * Set whether to cache all queries executed by this template. + *
If this is "true", all Query and Criteria objects created by + * this template will be marked as cacheable (including all + * queries through find methods). + *
To specify the query region to be used for queries cached + * by this template, set the "queryCacheRegion" property. + * @see #setQueryCacheRegion + * @see org.hibernate.Query#setCacheable + * @see org.hibernate.Criteria#setCacheable + */ + public void setCacheQueries(boolean cacheQueries) { + this.cacheQueries = cacheQueries; + } + + /** + * Return whether to cache all queries executed by this template. + */ + public boolean isCacheQueries() { + return this.cacheQueries; + } + + /** + * Set the name of the cache region for queries executed by this template. + *
If this is specified, it will be applied to all Query and Criteria objects + * created by this template (including all queries through find methods). + *
The cache region will not take effect unless queries created by this + * template are configured to be cached via the "cacheQueries" property. + * @see #setCacheQueries + * @see org.hibernate.Query#setCacheRegion + * @see org.hibernate.Criteria#setCacheRegion + */ + public void setQueryCacheRegion(String queryCacheRegion) { + this.queryCacheRegion = queryCacheRegion; + } + + /** + * Return the name of the cache region for queries executed by this template. + */ + public String getQueryCacheRegion() { + return this.queryCacheRegion; + } + + /** + * Set the fetch size for this HibernateTemplate. This is important for processing + * large result sets: Setting this higher than the default value will increase + * processing speed at the cost of memory consumption; setting this lower can + * avoid transferring row data that will never be read by the application. + *
Default is 0, indicating to use the JDBC driver's default. + */ + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + /** + * Return the fetch size specified for this HibernateTemplate. + */ + public int getFetchSize() { + return this.fetchSize; + } + + /** + * Set the maximum number of rows for this HibernateTemplate. This is important + * for processing subsets of large result sets, avoiding to read and hold + * the entire result set in the database or in the JDBC driver if we're + * never interested in the entire result in the first place (for example, + * when performing searches that might return a large number of matches). + *
Default is 0, indicating to use the JDBC driver's default. + */ + public void setMaxResults(int maxResults) { + this.maxResults = maxResults; + } + + /** + * Return the maximum number of rows specified for this HibernateTemplate. + */ + public int getMaxResults() { + return this.maxResults; + } + + + public Object execute(HibernateCallback action) throws DataAccessException { + return doExecute(action, false, false); + } + + public List executeFind(HibernateCallback action) throws DataAccessException { + Object result = doExecute(action, false, false); + if (result != null && !(result instanceof List)) { + throw new InvalidDataAccessApiUsageException( + "Result object returned from HibernateCallback isn't a List: [" + result + "]"); + } + return (List) result; + } + + /** + * Execute the action specified by the given action object within a + * new {@link org.hibernate.Session}. + *
This execute variant overrides the template-wide
+ * {@link #isAlwaysUseNewSession() "alwaysUseNewSession"} setting.
+ * @param action callback object that specifies the Hibernate action
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ */
+ public Object executeWithNewSession(HibernateCallback action) {
+ return doExecute(action, true, false);
+ }
+
+ /**
+ * Execute the action specified by the given action object within a
+ * native {@link org.hibernate.Session}.
+ *
This execute variant overrides the template-wide
+ * {@link #isExposeNativeSession() "exposeNativeSession"} setting.
+ * @param action callback object that specifies the Hibernate action
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ */
+ public Object executeWithNativeSession(HibernateCallback action) {
+ return doExecute(action, false, true);
+ }
+
+ /**
+ * Execute the action specified by the given action object within a Session.
+ * @param action callback object that specifies the Hibernate action
+ * @param enforceNativeSession whether to enforce exposure of the native
+ * Hibernate Session to callback code
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ * @deprecated as of Spring 2.5, in favor of {@link #executeWithNativeSession}
+ */
+ public Object execute(HibernateCallback action, boolean enforceNativeSession) throws DataAccessException {
+ return doExecute(action, false, enforceNativeSession);
+ }
+
+ /**
+ * Execute the action specified by the given action object within a Session.
+ * @param action callback object that specifies the Hibernate action
+ * @param enforceNewSession whether to enforce a new Session for this template
+ * even if there is a pre-bound transactional Session
+ * @param enforceNativeSession whether to enforce exposure of the native
+ * Hibernate Session to callback code
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
+ */
+ protected Object doExecute(HibernateCallback action, boolean enforceNewSession, boolean enforceNativeSession)
+ throws DataAccessException {
+
+ Assert.notNull(action, "Callback object must not be null");
+
+ Session session = (enforceNewSession ?
+ SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession());
+ boolean existingTransaction = (!enforceNewSession &&
+ (!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
+ if (existingTransaction) {
+ logger.debug("Found thread-bound Session for HibernateTemplate");
+ }
+
+ FlushMode previousFlushMode = null;
+ try {
+ previousFlushMode = applyFlushMode(session, existingTransaction);
+ enableFilters(session);
+ Session sessionToExpose =
+ (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
+ Object result = action.doInHibernate(sessionToExpose);
+ flushIfNecessary(session, existingTransaction);
+ return result;
+ }
+ catch (HibernateException ex) {
+ throw convertHibernateAccessException(ex);
+ }
+ catch (SQLException ex) {
+ throw convertJdbcAccessException(ex);
+ }
+ catch (RuntimeException ex) {
+ // Callback code threw application exception...
+ throw ex;
+ }
+ finally {
+ if (existingTransaction) {
+ logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate");
+ disableFilters(session);
+ if (previousFlushMode != null) {
+ session.setFlushMode(previousFlushMode);
+ }
+ }
+ else {
+ // Never use deferred close for an explicitly new Session.
+ if (isAlwaysUseNewSession()) {
+ SessionFactoryUtils.closeSession(session);
+ }
+ else {
+ SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a Session for use by this template.
+ *
Returns a new Session in case of "alwaysUseNewSession" (using the same
+ * JDBC Connection as a transactional Session, if applicable), a pre-bound
+ * Session in case of "allowCreate" turned off, and a pre-bound or new Session
+ * otherwise (new only if no transactional or otherwise pre-bound Session exists).
+ * @return the Session to use (never null)
+ * @see SessionFactoryUtils#getSession
+ * @see SessionFactoryUtils#getNewSession
+ * @see #setAlwaysUseNewSession
+ * @see #setAllowCreate
+ */
+ protected Session getSession() {
+ if (isAlwaysUseNewSession()) {
+ return SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor());
+ }
+ else if (isAllowCreate()) {
+ return SessionFactoryUtils.getSession(
+ getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
+ }
+ else if (SessionFactoryUtils.hasTransactionalSession(getSessionFactory())) {
+ return SessionFactoryUtils.getSession(getSessionFactory(), false);
+ }
+ else {
+ try {
+ return getSessionFactory().getCurrentSession();
+ }
+ catch (HibernateException ex) {
+ throw new DataAccessResourceFailureException("Could not obtain current Hibernate Session", ex);
+ }
+ }
+ }
+
+ /**
+ * Create a close-suppressing proxy for the given Hibernate Session.
+ * The proxy also prepares returned Query and Criteria objects.
+ * @param session the Hibernate Session to create a proxy for
+ * @return the Session proxy
+ * @see org.hibernate.Session#close()
+ * @see #prepareQuery
+ * @see #prepareCriteria
+ */
+ protected Session createSessionProxy(Session session) {
+ Class[] sessionIfcs = null;
+ Class mainIfc = (session instanceof org.hibernate.classic.Session ?
+ org.hibernate.classic.Session.class : Session.class);
+ if (session instanceof EventSource) {
+ sessionIfcs = new Class[] {mainIfc, EventSource.class};
+ }
+ else if (session instanceof SessionImplementor) {
+ sessionIfcs = new Class[] {mainIfc, SessionImplementor.class};
+ }
+ else {
+ sessionIfcs = new Class[] {mainIfc};
+ }
+ return (Session) Proxy.newProxyInstance(
+ session.getClass().getClassLoader(), sessionIfcs,
+ new CloseSuppressingInvocationHandler(session));
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for loading individual objects
+ //-------------------------------------------------------------------------
+
+ public Object get(Class entityClass, Serializable id) throws DataAccessException {
+ return get(entityClass, id, null);
+ }
+
+ public Object get(final Class entityClass, final Serializable id, final LockMode lockMode)
+ throws DataAccessException {
+
+ return executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ if (lockMode != null) {
+ return session.get(entityClass, id, lockMode);
+ }
+ else {
+ return session.get(entityClass, id);
+ }
+ }
+ });
+ }
+
+ public Object get(String entityName, Serializable id) throws DataAccessException {
+ return get(entityName, id, null);
+ }
+
+ public Object get(final String entityName, final Serializable id, final LockMode lockMode)
+ throws DataAccessException {
+
+ return executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ if (lockMode != null) {
+ return session.get(entityName, id, lockMode);
+ }
+ else {
+ return session.get(entityName, id);
+ }
+ }
+ });
+ }
+
+ public Object load(Class entityClass, Serializable id) throws DataAccessException {
+ return load(entityClass, id, null);
+ }
+
+ public Object load(final Class entityClass, final Serializable id, final LockMode lockMode)
+ throws DataAccessException {
+
+ return executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ if (lockMode != null) {
+ return session.load(entityClass, id, lockMode);
+ }
+ else {
+ return session.load(entityClass, id);
+ }
+ }
+ });
+ }
+
+ public Object load(String entityName, Serializable id) throws DataAccessException {
+ return load(entityName, id, null);
+ }
+
+ public Object load(final String entityName, final Serializable id, final LockMode lockMode)
+ throws DataAccessException {
+
+ return executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ if (lockMode != null) {
+ return session.load(entityName, id, lockMode);
+ }
+ else {
+ return session.load(entityName, id);
+ }
+ }
+ });
+ }
+
+ public List loadAll(final Class entityClass) throws DataAccessException {
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Criteria criteria = session.createCriteria(entityClass);
+ criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
+ prepareCriteria(criteria);
+ return criteria.list();
+ }
+ });
+ }
+
+ public void load(final Object entity, final Serializable id) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ session.load(entity, id);
+ return null;
+ }
+ });
+ }
+
+ public void refresh(final Object entity) throws DataAccessException {
+ refresh(entity, null);
+ }
+
+ public void refresh(final Object entity, final LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ if (lockMode != null) {
+ session.refresh(entity, lockMode);
+ }
+ else {
+ session.refresh(entity);
+ }
+ return null;
+ }
+ });
+ }
+
+ public boolean contains(final Object entity) throws DataAccessException {
+ Boolean result = (Boolean) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) {
+ return (session.contains(entity) ? Boolean.TRUE : Boolean.FALSE);
+ }
+ });
+ return result.booleanValue();
+ }
+
+ public void evict(final Object entity) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ session.evict(entity);
+ return null;
+ }
+ });
+ }
+
+ public void initialize(Object proxy) throws DataAccessException {
+ try {
+ Hibernate.initialize(proxy);
+ }
+ catch (HibernateException ex) {
+ throw SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+ }
+
+ public Filter enableFilter(String filterName) throws IllegalStateException {
+ Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
+ Filter filter = session.getEnabledFilter(filterName);
+ if (filter == null) {
+ filter = session.enableFilter(filterName);
+ }
+ return filter;
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for storing individual objects
+ //-------------------------------------------------------------------------
+
+ public void lock(final Object entity, final LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ session.lock(entity, lockMode);
+ return null;
+ }
+ });
+ }
+
+ public void lock(final String entityName, final Object entity, final LockMode lockMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ session.lock(entityName, entity, lockMode);
+ return null;
+ }
+ });
+ }
+
+ public Serializable save(final Object entity) throws DataAccessException {
+ return (Serializable) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ return session.save(entity);
+ }
+ });
+ }
+
+ public Serializable save(final String entityName, final Object entity) throws DataAccessException {
+ return (Serializable) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ return session.save(entityName, entity);
+ }
+ });
+ }
+
+ public void update(Object entity) throws DataAccessException {
+ update(entity, null);
+ }
+
+ public void update(final Object entity, final LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.update(entity);
+ if (lockMode != null) {
+ session.lock(entity, lockMode);
+ }
+ return null;
+ }
+ });
+ }
+
+ public void update(String entityName, Object entity) throws DataAccessException {
+ update(entityName, entity, null);
+ }
+
+ public void update(final String entityName, final Object entity, final LockMode lockMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.update(entityName, entity);
+ if (lockMode != null) {
+ session.lock(entity, lockMode);
+ }
+ return null;
+ }
+ });
+ }
+
+ public void saveOrUpdate(final Object entity) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.saveOrUpdate(entity);
+ return null;
+ }
+ });
+ }
+
+ public void saveOrUpdate(final String entityName, final Object entity) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.saveOrUpdate(entityName, entity);
+ return null;
+ }
+ });
+ }
+
+ public void saveOrUpdateAll(final Collection entities) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ for (Iterator it = entities.iterator(); it.hasNext();) {
+ session.saveOrUpdate(it.next());
+ }
+ return null;
+ }
+ });
+ }
+
+ public void replicate(final Object entity, final ReplicationMode replicationMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.replicate(entity, replicationMode);
+ return null;
+ }
+ });
+ }
+
+ public void replicate(final String entityName, final Object entity, final ReplicationMode replicationMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.replicate(entityName, entity, replicationMode);
+ return null;
+ }
+ });
+ }
+
+ public void persist(final Object entity) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.persist(entity);
+ return null;
+ }
+ });
+ }
+
+ public void persist(final String entityName, final Object entity) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ session.persist(entityName, entity);
+ return null;
+ }
+ });
+ }
+
+ public Object merge(final Object entity) throws DataAccessException {
+ return executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ return session.merge(entity);
+ }
+ });
+ }
+
+ public Object merge(final String entityName, final Object entity) throws DataAccessException {
+ return executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ return session.merge(entityName, entity);
+ }
+ });
+ }
+
+ public void delete(Object entity) throws DataAccessException {
+ delete(entity, null);
+ }
+
+ public void delete(final Object entity, final LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ if (lockMode != null) {
+ session.lock(entity, lockMode);
+ }
+ session.delete(entity);
+ return null;
+ }
+ });
+ }
+
+ public void delete(String entityName, Object entity) throws DataAccessException {
+ delete(entityName, entity, null);
+ }
+
+ public void delete(final String entityName, final Object entity, final LockMode lockMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ if (lockMode != null) {
+ session.lock(entityName, entity, lockMode);
+ }
+ session.delete(entityName, entity);
+ return null;
+ }
+ });
+ }
+
+ public void deleteAll(final Collection entities) throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ checkWriteOperationAllowed(session);
+ for (Iterator it = entities.iterator(); it.hasNext();) {
+ session.delete(it.next());
+ }
+ return null;
+ }
+ });
+ }
+
+ public void flush() throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ session.flush();
+ return null;
+ }
+ });
+ }
+
+ public void clear() throws DataAccessException {
+ executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) {
+ session.clear();
+ return null;
+ }
+ });
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods for HQL strings
+ //-------------------------------------------------------------------------
+
+ public List find(String queryString) throws DataAccessException {
+ return find(queryString, (Object[]) null);
+ }
+
+ public List find(String queryString, Object value) throws DataAccessException {
+ return find(queryString, new Object[] {value});
+ }
+
+ public List find(final String queryString, final Object[] values) throws DataAccessException {
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ queryObject.setParameter(i, values[i]);
+ }
+ }
+ return queryObject.list();
+ }
+ });
+ }
+
+ public List findByNamedParam(String queryString, String paramName, Object value)
+ throws DataAccessException {
+
+ return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value});
+ }
+
+ public List findByNamedParam(final String queryString, final String[] paramNames, final Object[] values)
+ throws DataAccessException {
+
+ if (paramNames.length != values.length) {
+ throw new IllegalArgumentException("Length of paramNames array must match length of values array");
+ }
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
+ }
+ }
+ return queryObject.list();
+ }
+ });
+ }
+
+ public List findByValueBean(final String queryString, final Object valueBean)
+ throws DataAccessException {
+
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ queryObject.setProperties(valueBean);
+ return queryObject.list();
+ }
+ });
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods for named queries
+ //-------------------------------------------------------------------------
+
+ public List findByNamedQuery(String queryName) throws DataAccessException {
+ return findByNamedQuery(queryName, (Object[]) null);
+ }
+
+ public List findByNamedQuery(String queryName, Object value) throws DataAccessException {
+ return findByNamedQuery(queryName, new Object[] {value});
+ }
+
+ public List findByNamedQuery(final String queryName, final Object[] values) throws DataAccessException {
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.getNamedQuery(queryName);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ queryObject.setParameter(i, values[i]);
+ }
+ }
+ return queryObject.list();
+ }
+ });
+ }
+
+ public List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
+ throws DataAccessException {
+
+ return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value});
+ }
+
+ public List findByNamedQueryAndNamedParam(
+ final String queryName, final String[] paramNames, final Object[] values)
+ throws DataAccessException {
+
+ if (paramNames != null && values != null && paramNames.length != values.length) {
+ throw new IllegalArgumentException("Length of paramNames array must match length of values array");
+ }
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.getNamedQuery(queryName);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
+ }
+ }
+ return queryObject.list();
+ }
+ });
+ }
+
+ public List findByNamedQueryAndValueBean(final String queryName, final Object valueBean)
+ throws DataAccessException {
+
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.getNamedQuery(queryName);
+ prepareQuery(queryObject);
+ queryObject.setProperties(valueBean);
+ return queryObject.list();
+ }
+ });
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods for detached criteria
+ //-------------------------------------------------------------------------
+
+ public List findByCriteria(DetachedCriteria criteria) throws DataAccessException {
+ return findByCriteria(criteria, -1, -1);
+ }
+
+ public List findByCriteria(final DetachedCriteria criteria, final int firstResult, final int maxResults)
+ throws DataAccessException {
+
+ Assert.notNull(criteria, "DetachedCriteria must not be null");
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Criteria executableCriteria = criteria.getExecutableCriteria(session);
+ prepareCriteria(executableCriteria);
+ if (firstResult >= 0) {
+ executableCriteria.setFirstResult(firstResult);
+ }
+ if (maxResults > 0) {
+ executableCriteria.setMaxResults(maxResults);
+ }
+ return executableCriteria.list();
+ }
+ });
+ }
+
+ public List findByExample(Object exampleEntity) throws DataAccessException {
+ return findByExample(null, exampleEntity, -1, -1);
+ }
+
+ public List findByExample(String entityName, Object exampleEntity) throws DataAccessException {
+ return findByExample(entityName, exampleEntity, -1, -1);
+ }
+
+ public List findByExample(Object exampleEntity, int firstResult, int maxResults) throws DataAccessException {
+ return findByExample(null, exampleEntity, firstResult, maxResults);
+ }
+
+ public List findByExample(
+ final String entityName, final Object exampleEntity, final int firstResult, final int maxResults)
+ throws DataAccessException {
+
+ Assert.notNull(exampleEntity, "Example entity must not be null");
+ return (List) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Criteria executableCriteria = (entityName != null ?
+ session.createCriteria(entityName) : session.createCriteria(exampleEntity.getClass()));
+ executableCriteria.add(Example.create(exampleEntity));
+ prepareCriteria(executableCriteria);
+ if (firstResult >= 0) {
+ executableCriteria.setFirstResult(firstResult);
+ }
+ if (maxResults > 0) {
+ executableCriteria.setMaxResults(maxResults);
+ }
+ return executableCriteria.list();
+ }
+ });
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience query methods for iteration and bulk updates/deletes
+ //-------------------------------------------------------------------------
+
+ public Iterator iterate(String queryString) throws DataAccessException {
+ return iterate(queryString, (Object[]) null);
+ }
+
+ public Iterator iterate(String queryString, Object value) throws DataAccessException {
+ return iterate(queryString, new Object[] {value});
+ }
+
+ public Iterator iterate(final String queryString, final Object[] values) throws DataAccessException {
+ return (Iterator) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ queryObject.setParameter(i, values[i]);
+ }
+ }
+ return queryObject.iterate();
+ }
+ });
+ }
+
+ public void closeIterator(Iterator it) throws DataAccessException {
+ try {
+ Hibernate.close(it);
+ }
+ catch (HibernateException ex) {
+ throw SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+ }
+
+ public int bulkUpdate(String queryString) throws DataAccessException {
+ return bulkUpdate(queryString, (Object[]) null);
+ }
+
+ public int bulkUpdate(String queryString, Object value) throws DataAccessException {
+ return bulkUpdate(queryString, new Object[] {value});
+ }
+
+ public int bulkUpdate(final String queryString, final Object[] values) throws DataAccessException {
+ Integer updateCount = (Integer) executeWithNativeSession(new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException {
+ Query queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ queryObject.setParameter(i, values[i]);
+ }
+ }
+ return new Integer(queryObject.executeUpdate());
+ }
+ });
+ return updateCount.intValue();
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Helper methods used by the operations above
+ //-------------------------------------------------------------------------
+
+ /**
+ * Check whether write operations are allowed on the given Session.
+ *
Default implementation throws an InvalidDataAccessApiUsageException in
+ * case of FlushMode.NEVER/MANUAL. Can be overridden in subclasses.
+ * @param session current Hibernate Session
+ * @throws InvalidDataAccessApiUsageException if write operations are not allowed
+ * @see #setCheckWriteOperations
+ * @see #getFlushMode()
+ * @see #FLUSH_EAGER
+ * @see org.hibernate.Session#getFlushMode()
+ * @see org.hibernate.FlushMode#NEVER
+ * @see org.hibernate.FlushMode#MANUAL
+ */
+ protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
+ if (isCheckWriteOperations() && getFlushMode() != FLUSH_EAGER &&
+ session.getFlushMode().lessThan(FlushMode.COMMIT)) {
+ throw new InvalidDataAccessApiUsageException(
+ "Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): "+
+ "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
+ }
+ }
+
+ /**
+ * Prepare the given Query object, applying cache settings and/or
+ * a transaction timeout.
+ * @param queryObject the Query object to prepare
+ * @see #setCacheQueries
+ * @see #setQueryCacheRegion
+ * @see SessionFactoryUtils#applyTransactionTimeout
+ */
+ protected void prepareQuery(Query queryObject) {
+ if (isCacheQueries()) {
+ queryObject.setCacheable(true);
+ if (getQueryCacheRegion() != null) {
+ queryObject.setCacheRegion(getQueryCacheRegion());
+ }
+ }
+ if (getFetchSize() > 0) {
+ queryObject.setFetchSize(getFetchSize());
+ }
+ if (getMaxResults() > 0) {
+ queryObject.setMaxResults(getMaxResults());
+ }
+ SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
+ }
+
+ /**
+ * Prepare the given Criteria object, applying cache settings and/or
+ * a transaction timeout.
+ * @param criteria the Criteria object to prepare
+ * @see #setCacheQueries
+ * @see #setQueryCacheRegion
+ * @see SessionFactoryUtils#applyTransactionTimeout
+ */
+ protected void prepareCriteria(Criteria criteria) {
+ if (isCacheQueries()) {
+ criteria.setCacheable(true);
+ if (getQueryCacheRegion() != null) {
+ criteria.setCacheRegion(getQueryCacheRegion());
+ }
+ }
+ if (getFetchSize() > 0) {
+ criteria.setFetchSize(getFetchSize());
+ }
+ if (getMaxResults() > 0) {
+ criteria.setMaxResults(getMaxResults());
+ }
+ SessionFactoryUtils.applyTransactionTimeout(criteria, getSessionFactory());
+ }
+
+ /**
+ * Apply the given name parameter to the given Query object.
+ * @param queryObject the Query object
+ * @param paramName the name of the parameter
+ * @param value the value of the parameter
+ * @throws HibernateException if thrown by the Query object
+ */
+ protected void applyNamedParameterToQuery(Query queryObject, String paramName, Object value)
+ throws HibernateException {
+
+ if (value instanceof Collection) {
+ queryObject.setParameterList(paramName, (Collection) value);
+ }
+ else if (value instanceof Object[]) {
+ queryObject.setParameterList(paramName, (Object[]) value);
+ }
+ else {
+ queryObject.setParameter(paramName, value);
+ }
+ }
+
+
+ /**
+ * Invocation handler that suppresses close calls on Hibernate Sessions.
+ * Also prepares returned Query and Criteria objects.
+ * @see org.hibernate.Session#close
+ */
+ private class CloseSuppressingInvocationHandler implements InvocationHandler {
+
+ private final Session target;
+
+ public CloseSuppressingInvocationHandler(Session target) {
+ this.target = target;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Invocation on Session interface coming in...
+
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of Session proxy.
+ return new Integer(System.identityHashCode(proxy));
+ }
+ else if (method.getName().equals("close")) {
+ // Handle close method: suppress, not valid.
+ return null;
+ }
+
+ // Invoke method on target Session.
+ try {
+ Object retVal = method.invoke(this.target, args);
+
+ // If return value is a Query or Criteria, apply transaction timeout.
+ // Applies to createQuery, getNamedQuery, createCriteria.
+ if (retVal instanceof Query) {
+ prepareQuery(((Query) retVal));
+ }
+ if (retVal instanceof Criteria) {
+ prepareCriteria(((Criteria) retVal));
+ }
+
+ return retVal;
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java
new file mode 100644
index 00000000000..69dd16fe63e
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import org.hibernate.ConnectionReleaseMode;
+import org.hibernate.FlushMode;
+import org.hibernate.HibernateException;
+import org.hibernate.Interceptor;
+import org.hibernate.JDBCException;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.exception.GenericJDBCException;
+import org.hibernate.impl.SessionImpl;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+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.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
+import org.springframework.jdbc.support.SQLExceptionTranslator;
+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. {@link SessionFactoryUtils}
+ * and {@link HibernateTemplate} are aware of thread-bound Sessions and participate
+ * in such transactions automatically. Using either of those or going through
+ * SessionFactory.getCurrentSession() is required for Hibernate
+ * access code that needs to support this transaction handling mechanism.
+ *
+ *
Supports custom isolation levels, and timeouts that get applied as + * Hibernate transaction timeouts. + * + *
This transaction manager is appropriate for applications that use a single + * Hibernate SessionFactory for transactional data access, but it also supports + * direct DataSource access within a transaction (i.e. plain JDBC code working + * with the same DataSource). This allows for mixing services which access Hibernate + * and services which use plain JDBC (without being aware of Hibernate)! + * Application code needs to stick to the same simple Connection lookup pattern as + * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection} + * or going through a + * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}). + * + *
Note: To be able to register a DataSource's Connection for plain JDBC code, + * this instance needs to be aware of the DataSource ({@link #setDataSource}). + * The given DataSource should obviously match the one used by the given + * SessionFactory. To achieve this, configure both to the same JNDI DataSource, + * or preferably create the SessionFactory with {@link LocalSessionFactoryBean} and + * a local DataSource (which will be autodetected by this transaction manager). + * + *
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). Normally, JTA setup for Hibernate is + * somewhat container-specific due to the JTA TransactionManager lookup, required + * for proper transactional handling of the SessionFactory-level read-write cache. + * + *
Fortunately, there is an easier way with Spring: {@link SessionFactoryUtils} + * (and thus {@link HibernateTemplate}) registers synchronizations with Spring's + * {@link org.springframework.transaction.support.TransactionSynchronizationManager} + * (as used by {@link org.springframework.transaction.jta.JtaTransactionManager}), + * for proper after-completion callbacks. Therefore, as long as Spring's + * JtaTransactionManager drives the JTA transactions, Hibernate does not require + * any special configuration for proper JTA participation. Note that there are + * special restrictions with EJB CMT and restrictive JTA subsystems: See + * {@link org.springframework.transaction.jta.JtaTransactionManager}'s javadoc for details. + * + *
On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0 + * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} + * flag defaults to "false", though, as nested transactions will just apply to the + * JDBC Connection, not to the Hibernate Session and its cached objects. You can + * manually set the flag to "true" if you want to use nested transactions for + * JDBC access code which participates in Hibernate transactions (provided that + * your JDBC driver supports Savepoints). Note that Hibernate itself does not + * support nested transactions! Hence, do not expect Hibernate access code to + * semantically participate in a nested transaction. + * + *
Requires Hibernate 3.1 or later, as of Spring 2.5. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setSessionFactory + * @see #setDataSource + * @see LocalSessionFactoryBean + * @see SessionFactoryUtils#getSession + * @see SessionFactoryUtils#applyTransactionTimeout + * @see SessionFactoryUtils#releaseSession + * @see HibernateTemplate + * @see org.hibernate.SessionFactory#getCurrentSession() + * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection + * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout + * @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 + */ +public class HibernateTransactionManager extends AbstractPlatformTransactionManager + implements ResourceTransactionManager, BeanFactoryAware, InitializingBean { + + private SessionFactory sessionFactory; + + private DataSource dataSource; + + private boolean autodetectDataSource = true; + + private boolean prepareConnection = true; + + private boolean hibernateManagedSession = false; + + private boolean earlyFlushBeforeCommit = false; + + private Object entityInterceptor; + + private SQLExceptionTranslator jdbcExceptionTranslator; + + private SQLExceptionTranslator defaultJdbcExceptionTranslator; + + /** + * Just needed for entityInterceptorBeanName. + * @see #setEntityInterceptorBeanName + */ + private BeanFactory beanFactory; + + + /** + * Create a new HibernateTransactionManager instance. + * A SessionFactory has to be set to be able to use it. + * @see #setSessionFactory + */ + public HibernateTransactionManager() { + } + + /** + * Create a new HibernateTransactionManager instance. + * @param sessionFactory SessionFactory to manage transactions for + */ + public HibernateTransactionManager(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + afterPropertiesSet(); + } + + + /** + * Set the SessionFactory that this instance should manage transactions for. + */ + public void setSessionFactory(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + /** + * Return the SessionFactory that this instance should manage transactions for. + */ + public SessionFactory getSessionFactory() { + return this.sessionFactory; + } + + /** + * Set the JDBC DataSource that this instance should manage transactions for. + * The DataSource should match the one used by the Hibernate SessionFactory: + * for example, you could specify the same JNDI DataSource for both. + *
If the SessionFactory was configured with LocalDataSourceConnectionProvider, + * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource", + * the DataSource will be auto-detected: You can still explictly specify the + * DataSource, but you don't need to in this case. + *
A transactional JDBC Connection for this DataSource will be provided to + * application code accessing this DataSource directly via DataSourceUtils + * or JdbcTemplate. The Connection will be taken from the Hibernate Session. + *
The DataSource specified here should be the target DataSource to manage
+ * transactions for, not a TransactionAwareDataSourceProxy. Only data access
+ * code may work with TransactionAwareDataSourceProxy, while the transaction
+ * manager needs to work on the underlying target DataSource. If there's
+ * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
+ * unwrapped to extract its target DataSource.
+ * @see #setAutodetectDataSource
+ * @see LocalDataSourceConnectionProvider
+ * @see LocalSessionFactoryBean#setDataSource
+ * @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 LocalSessionFactoryBean's setDataSource. Default is "true".
+ *
Can be turned off to deliberately ignore an available DataSource, in order + * to not expose Hibernate transactions as JDBC transactions for that DataSource. + * @see #setDataSource + * @see LocalSessionFactoryBean#setDataSource + */ + public void setAutodetectDataSource(boolean autodetectDataSource) { + this.autodetectDataSource = autodetectDataSource; + } + + /** + * Set whether to prepare the underlying JDBC Connection of a transactional + * Hibernate Session, that is, whether to apply a transaction-specific + * isolation level and/or the transaction's read-only flag to the underlying + * JDBC Connection. + *
Default is "true". If you turn this flag off, the transaction manager
+ * will not support per-transaction isolation levels anymore. It will not
+ * call Connection.setReadOnly(true) for read-only transactions
+ * anymore either. If this flag is turned off, no cleanup of a JDBC Connection
+ * is required after a transaction, since no Connection settings will get modified.
+ *
It is recommended to turn this flag off if running against Hibernate 3.1 + * and a connection pool that does not reset connection settings (for example, + * Jakarta Commons DBCP). To keep this flag turned on, you can set the + * "hibernate.connection.release_mode" property to "on_close" instead, + * or consider using a smarter connection pool (for example, C3P0). + * @see java.sql.Connection#setTransactionIsolation + * @see java.sql.Connection#setReadOnly + */ + public void setPrepareConnection(boolean prepareConnection) { + this.prepareConnection = prepareConnection; + } + + /** + * Set whether to operate on a Hibernate-managed Session instead of a + * Spring-managed Session, that is, whether to obtain the Session through + * Hibernate's {@link org.hibernate.SessionFactory#getCurrentSession()} + * instead of {@link org.hibernate.SessionFactory#openSession()} (with a Spring + * {@link org.springframework.transaction.support.TransactionSynchronizationManager} + * check preceding it). + *
Default is "false", i.e. using a Spring-managed Session: taking the current + * thread-bound Session if available (e.g. in an Open-Session-in-View scenario), + * creating a new Session for the current transaction otherwise. + *
Switch this flag to "true" in order to enforce use of a Hibernate-managed Session.
+ * Note that this requires {@link org.hibernate.SessionFactory#getCurrentSession()}
+ * to always return a proper Session when called for a Spring-managed transaction;
+ * transaction begin will fail if the getCurrentSession() call fails.
+ *
This mode will typically be used in combination with a custom Hibernate
+ * {@link org.hibernate.context.CurrentSessionContext} implementation that stores
+ * Sessions in a place other than Spring's TransactionSynchronizationManager.
+ * It may also be used in combination with Spring's Open-Session-in-View support
+ * (using Spring's default {@link SpringSessionContext}), in which case it subtly
+ * differs from the Spring-managed Session mode: The pre-bound Session will not
+ * receive a clear() call (on rollback) or a disconnect()
+ * call (on transaction completion) in such a scenario; this is rather left up
+ * to a custom CurrentSessionContext implementation (if desired).
+ */
+ public void setHibernateManagedSession(boolean hibernateManagedSession) {
+ this.hibernateManagedSession = hibernateManagedSession;
+ }
+
+ /**
+ * Set whether to perform an early flush before proceeding with a commit.
+ *
Default is "false", performing an implicit flush as part of the
+ * actual commit step. Switch this to "true" in order to enforce an
+ * explicit flush before the before-commit synchronization phase, making
+ * flushed state visible to beforeCommit callbacks of registered
+ * {@link org.springframework.transaction.support.TransactionSynchronization}
+ * objects.
+ *
Such explicit flush behavior is also consistent with Spring-driven + * flushing in a JTA transaction environment, so may also be enforced for + * consistency with JTA transaction behavior. + * @see #prepareForCommit + */ + public void setEarlyFlushBeforeCommit(boolean earlyFlushBeforeCommit) { + this.earlyFlushBeforeCommit = earlyFlushBeforeCommit; + } + + /** + * Set the bean name of a Hibernate entity interceptor that allows to inspect + * and change property values before writing to and reading from the database. + * Will get applied to any new Session created by this transaction manager. + *
Requires the bean factory to be known, to be able to resolve the bean + * name to an interceptor instance on session creation. Typically used for + * prototype interceptors, i.e. a new interceptor instance per session. + *
Can also be used for shared interceptor instances, but it is recommended + * to set the interceptor reference directly in such a scenario. + * @param entityInterceptorBeanName the name of the entity interceptor in + * the bean factory + * @see #setBeanFactory + * @see #setEntityInterceptor + */ + public void setEntityInterceptorBeanName(String entityInterceptorBeanName) { + this.entityInterceptor = entityInterceptorBeanName; + } + + /** + * Set a Hibernate entity interceptor that allows to inspect and change + * property values before writing to and reading from the database. + * Will get applied to any new Session created by this transaction manager. + *
Such an interceptor can either be set at the SessionFactory level,
+ * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
+ * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
+ * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
+ * to avoid repeated configuration and guarantee consistent behavior in transactions.
+ * @see LocalSessionFactoryBean#setEntityInterceptor
+ * @see HibernateTemplate#setEntityInterceptor
+ * @see HibernateInterceptor#setEntityInterceptor
+ */
+ public void setEntityInterceptor(Interceptor entityInterceptor) {
+ this.entityInterceptor = entityInterceptor;
+ }
+
+ /**
+ * Return the current Hibernate entity interceptor, or null if none.
+ * Resolves an entity interceptor bean name via the bean factory,
+ * if necessary.
+ * @throws IllegalStateException if bean name specified but no bean factory set
+ * @throws BeansException if bean name resolution via the bean factory failed
+ * @see #setEntityInterceptor
+ * @see #setEntityInterceptorBeanName
+ * @see #setBeanFactory
+ */
+ public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
+ if (this.entityInterceptor instanceof Interceptor) {
+ return (Interceptor) entityInterceptor;
+ }
+ else if (this.entityInterceptor instanceof String) {
+ if (this.beanFactory == null) {
+ throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
+ }
+ String beanName = (String) this.entityInterceptor;
+ return (Interceptor) this.beanFactory.getBean(beanName, Interceptor.class);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Set the JDBC exception translator for this transaction manager.
+ *
Applied to any SQLException root cause of a Hibernate JDBCException that + * is thrown on flush, overriding Hibernate's default SQLException translation + * (which is based on Hibernate's Dialect for a specific target database). + * @param jdbcExceptionTranslator the exception translator + * @see java.sql.SQLException + * @see org.hibernate.JDBCException + * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator + * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator + */ + public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { + this.jdbcExceptionTranslator = jdbcExceptionTranslator; + } + + /** + * Return the JDBC exception translator for this transaction manager, if any. + */ + public SQLExceptionTranslator getJdbcExceptionTranslator() { + return this.jdbcExceptionTranslator; + } + + /** + * The bean factory just needs to be known for resolving entity interceptor + * bean names. It does not need to be set for any other mode of operation. + * @see #setEntityInterceptorBeanName + */ + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public void afterPropertiesSet() { + if (getSessionFactory() == null) { + throw new IllegalArgumentException("Property 'sessionFactory' is required"); + } + if (this.entityInterceptor instanceof String && this.beanFactory == null) { + throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'"); + } + + // 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(); + } + + 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 [" + + SessionFactoryUtils.toString(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 [" + + SessionFactoryUtils.toString(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; + } + + protected boolean isExistingTransaction(Object transaction) { + HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; + return (txObject.hasSpringManagedTransaction() || + (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction())); + } + + 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()) { + Interceptor entityInterceptor = getEntityInterceptor(); + Session newSession = (entityInterceptor != null ? + getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession()); + if (logger.isDebugEnabled()) { + logger.debug("Opened new Session [" + SessionFactoryUtils.toString(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 [" + SessionFactoryUtils.toString(session) + "]"); + } + Connection con = 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). " + + "Make sure that your LocalSessionFactoryBean actually uses SpringTransactionFactory: Your " + + "Hibernate properties should *not* include a 'hibernate.transaction.factory_class' property!"); + } + if (logger.isDebugEnabled()) { + logger.debug( + "Not preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]"); + } + } + + if (definition.isReadOnly() && txObject.isNewSession()) { + // Just set to NEVER in case of a new Session for this transaction. + session.setFlushMode(FlushMode.NEVER); + } + + if (!definition.isReadOnly() && !txObject.isNewSession()) { + // We need AUTO or COMMIT for a non-read-only transaction. + FlushMode flushMode = session.getFlushMode(); + if (flushMode.lessThan(FlushMode.COMMIT)) { + session.setFlushMode(FlushMode.AUTO); + txObject.getSessionHolder().setPreviousFlushMode(flushMode); + } + } + + Transaction hibTx = null; + + // 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 = 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); + } + } + + 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); + } + + 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()); + } + } + + protected void prepareForCommit(DefaultTransactionStatus status) { + if (this.earlyFlushBeforeCommit) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + Session session = txObject.getSessionHolder().getSession(); + if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) { + logger.debug("Performing an early flush for Hibernate transaction"); + try { + session.flush(); + } + catch (HibernateException ex) { + throw convertHibernateAccessException(ex); + } + finally { + session.setFlushMode(FlushMode.NEVER); + } + } + } + } + + protected void doCommit(DefaultTransactionStatus status) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Committing Hibernate transaction on Session [" + + SessionFactoryUtils.toString(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); + } + } + + protected void doRollback(DefaultTransactionStatus status) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Rolling back Hibernate transaction on Session [" + + SessionFactoryUtils.toString(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(); + } + } + } + + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Setting Hibernate transaction on Session [" + + SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "] rollback-only"); + } + txObject.setRollbackOnly(); + } + + 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 = 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 [" + SessionFactoryUtils.toString(session) + + "] after transaction"); + } + SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory()); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Not closing pre-bound Hibernate Session [" + + SessionFactoryUtils.toString(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. + *
Default implementation checks the Session's connection release mode
+ * to be "on_close". Unfortunately, this requires casting to SessionImpl,
+ * as of Hibernate 3.1. If that cast doesn't work, we'll simply assume
+ * we're safe and return true.
+ * @param session the Hibernate Session to check
+ * @see org.hibernate.impl.SessionImpl#getConnectionReleaseMode()
+ * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE
+ */
+ protected boolean isSameConnectionForEntireSession(Session session) {
+ if (!(session instanceof SessionImpl)) {
+ // The best we can do is to assume we're safe.
+ return true;
+ }
+ ConnectionReleaseMode releaseMode = ((SessionImpl) session).getConnectionReleaseMode();
+ return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
+ }
+
+
+ /**
+ * Convert the given HibernateException to an appropriate exception
+ * from the org.springframework.dao hierarchy.
+ *
Will automatically apply a specified SQLExceptionTranslator to a
+ * Hibernate JDBCException, else rely on Hibernate's default translation.
+ * @param ex HibernateException that occured
+ * @return a corresponding DataAccessException
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ * @see #setJdbcExceptionTranslator
+ */
+ protected DataAccessException convertHibernateAccessException(HibernateException ex) {
+ if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
+ return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
+ }
+ else if (GenericJDBCException.class.equals(ex.getClass())) {
+ return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
+ }
+ return SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+
+ /**
+ * Convert the given Hibernate JDBCException to an appropriate exception
+ * from the org.springframework.dao hierarchy, using the
+ * given SQLExceptionTranslator.
+ * @param ex Hibernate JDBCException that occured
+ * @param translator the SQLExceptionTranslator to use
+ * @return a corresponding DataAccessException
+ */
+ protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
+ return translator.translate("Hibernate flushing: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
+ }
+
+ /**
+ * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
+ *
Creates a default
+ * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
+ * for the SessionFactory's underlying DataSource.
+ */
+ protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
+ if (this.defaultJdbcExceptionTranslator == null) {
+ if (getDataSource() != null) {
+ this.defaultJdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(getDataSource());
+ }
+ else {
+ this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
+ }
+ }
+ return this.defaultJdbcExceptionTranslator;
+ }
+
+
+ /**
+ * Hibernate transaction object, representing a SessionHolder.
+ * Used as transaction object by HibernateTransactionManager.
+ */
+ private static 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() {
+ getSessionHolder().setRollbackOnly();
+ if (hasConnectionHolder()) {
+ getConnectionHolder().setRollbackOnly();
+ }
+ }
+
+ public boolean isRollbackOnly() {
+ return getSessionHolder().isRollbackOnly() ||
+ (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
+ }
+ }
+
+
+ /**
+ * Holder for suspended resources.
+ * Used internally by doSuspend and doResume.
+ */
+ private static class SuspendedResourcesHolder {
+
+ private final SessionHolder sessionHolder;
+
+ private final ConnectionHolder connectionHolder;
+
+ private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
+ this.sessionHolder = sessionHolder;
+ this.connectionHolder = conHolder;
+ }
+
+ private SessionHolder getSessionHolder() {
+ return this.sessionHolder;
+ }
+
+ private ConnectionHolder getConnectionHolder() {
+ return this.connectionHolder;
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java
new file mode 100644
index 00000000000..a61273740a0
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.util.Properties;
+
+import org.hibernate.cache.Cache;
+import org.hibernate.cache.CacheException;
+import org.hibernate.cache.CacheProvider;
+
+/**
+ * Proxy for a Hibernate CacheProvider, delegating to a Spring-managed
+ * CacheProvider instance, determined by LocalSessionFactoryBean's
+ * "cacheProvider" property.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.1
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setCacheProvider
+ */
+public class LocalCacheProviderProxy implements CacheProvider {
+
+ private final CacheProvider cacheProvider;
+
+
+ public LocalCacheProviderProxy() {
+ CacheProvider cp = LocalSessionFactoryBean.getConfigTimeCacheProvider();
+ // absolutely needs thread-bound CacheProvider to initialize
+ if (cp == null) {
+ throw new IllegalStateException("No Hibernate CacheProvider found - " +
+ "'cacheProvider' property must be set on LocalSessionFactoryBean");
+ }
+ this.cacheProvider = cp;
+ }
+
+
+ public Cache buildCache(String regionName, Properties properties) throws CacheException {
+ return this.cacheProvider.buildCache(regionName, properties);
+ }
+
+ public long nextTimestamp() {
+ return this.cacheProvider.nextTimestamp();
+ }
+
+ public void start(Properties properties) throws CacheException {
+ this.cacheProvider.start(properties);
+ }
+
+ public void stop() {
+ this.cacheProvider.stop();
+ }
+
+ public boolean isMinimalPutsEnabledByDefault() {
+ return this.cacheProvider.isMinimalPutsEnabledByDefault();
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java
new file mode 100644
index 00000000000..9b3fbc37640
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.hibernate.HibernateException;
+import org.hibernate.connection.ConnectionProvider;
+import org.hibernate.util.JDBCExceptionReporter;
+
+/**
+ * Hibernate connection provider for local DataSource instances
+ * in an application context. This provider will be used if
+ * LocalSessionFactoryBean's "dataSource" property is set
+ * without a Hibernate TransactionManagerLookup.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see LocalSessionFactoryBean#setDataSource
+ */
+public class LocalDataSourceConnectionProvider implements ConnectionProvider {
+
+ private DataSource dataSource;
+
+ private DataSource dataSourceToUse;
+
+
+ public void configure(Properties props) throws HibernateException {
+ this.dataSource = LocalSessionFactoryBean.getConfigTimeDataSource();
+ // absolutely needs thread-bound DataSource to initialize
+ if (this.dataSource == null) {
+ throw new HibernateException("No local DataSource found for configuration - " +
+ "'dataSource' property must be set on LocalSessionFactoryBean");
+ }
+ this.dataSourceToUse = getDataSourceToUse(this.dataSource);
+ }
+
+ /**
+ * Return the DataSource to use for retrieving Connections.
+ *
This implementation returns the passed-in DataSource as-is.
+ * @param originalDataSource the DataSource as configured by the user
+ * on LocalSessionFactoryBean
+ * @return the DataSource to actually retrieve Connections from
+ * (potentially wrapped)
+ * @see LocalSessionFactoryBean#setDataSource
+ */
+ protected DataSource getDataSourceToUse(DataSource originalDataSource) {
+ return originalDataSource;
+ }
+
+ /**
+ * Return the DataSource that this ConnectionProvider wraps.
+ */
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * This implementation delegates to the underlying DataSource.
+ * @see javax.sql.DataSource#getConnection()
+ */
+ public Connection getConnection() throws SQLException {
+ try {
+ return this.dataSourceToUse.getConnection();
+ }
+ catch (SQLException ex) {
+ JDBCExceptionReporter.logExceptions(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * This implementation simply calls Connection.close.
+ * @see java.sql.Connection#close()
+ */
+ public void closeConnection(Connection con) throws SQLException {
+ try {
+ con.close();
+ }
+ catch (SQLException ex) {
+ JDBCExceptionReporter.logExceptions(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * This implementation does nothing:
+ * We're dealing with an externally managed DataSource.
+ */
+ public void close() {
+ }
+
+ /**
+ * This implementation returns false: We cannot guarantee
+ * to receive the same Connection within a transaction, not even when
+ * dealing with a JNDI DataSource.
+ */
+ public boolean supportsAggressiveRelease() {
+ return false;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java
new file mode 100644
index 00000000000..304fcbb432e
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+/**
+ * Subclass of LocalDataSourceConnectionProvider that will be used
+ * if LocalSessionFactoryBean's "dataSource" property is set
+ * in combination with a Hibernate TransactionManagerLookup.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.1
+ */
+public class LocalJtaDataSourceConnectionProvider extends LocalDataSourceConnectionProvider {
+
+ /**
+ * This implementation returns true,
+ * since we're assuming a JTA DataSource.
+ */
+ public boolean supportsAggressiveRelease() {
+ return true;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java
new file mode 100644
index 00000000000..08df08cfaa8
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java
@@ -0,0 +1,1024 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.io.File;
+import java.lang.reflect.Array;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+import javax.transaction.TransactionManager;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Interceptor;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.cache.CacheProvider;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.cfg.Mappings;
+import org.hibernate.cfg.NamingStrategy;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.FilterDefinition;
+import org.hibernate.event.EventListeners;
+import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
+import org.hibernate.transaction.JTATransactionFactory;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.jdbc.support.lob.LobHandler;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@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.
+ *
+ *
Configuration settings can either be read from a Hibernate XML file, + * specified as "configLocation", or completely via this class. A typical + * local configuration consists of one or more "mappingResources", various + * "hibernateProperties" (not strictly necessary), and a "dataSource" that the + * SessionFactory should use. The latter can also be specified via Hibernate + * properties, but "dataSource" supports any Spring-configured DataSource, + * instead of relying on Hibernate's own connection providers. + * + *
This SessionFactory handling strategy is appropriate for most types of + * applications, from Hibernate-only single database apps to ones that need + * distributed transactions. Either {@link HibernateTransactionManager} or + * {@link org.springframework.transaction.jta.JtaTransactionManager} can be + * used for transaction demarcation, with the latter only necessary for + * transactions which span multiple databases. + * + *
This factory bean will by default expose a transaction-aware SessionFactory
+ * proxy, letting data access code work with the plain Hibernate SessionFactory
+ * and its getCurrentSession() method, while still being able to
+ * participate in current Spring-managed transactions: with any transaction
+ * management strategy, either local or JTA / EJB CMT, and any transaction
+ * synchronization mechanism, either Spring or JTA. Furthermore,
+ * getCurrentSession() will also seamlessly work with
+ * a request-scoped Session managed by
+ * {@link org.springframework.orm.hibernate3.support.OpenSessionInViewFilter} /
+ * {@link org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor}.
+ *
+ *
Requires Hibernate 3.1 or later. Note that this factory will use + * "on_close" as default Hibernate connection release mode, unless in the + * case of a "jtaTransactionManager" specified, for the reason that + * this is appropriate for most Spring-based applications (in particular when + * using Spring's HibernateTransactionManager). Hibernate 3.0 used "on_close" + * as its own default too; however, Hibernate 3.1 changed this to "auto" + * (i.e. "after_statement" or "after_transaction"). + * + * @author Juergen Hoeller + * @since 1.2 + * @see HibernateTemplate#setSessionFactory + * @see HibernateTransactionManager#setSessionFactory + * @see #setExposeTransactionAwareSessionFactory + * @see #setJtaTransactionManager + * @see org.hibernate.SessionFactory#getCurrentSession() + * @see HibernateTransactionManager + */ +public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implements BeanClassLoaderAware { + + private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal(); + + private static final ThreadLocal configTimeTransactionManagerHolder = new ThreadLocal(); + + private static final ThreadLocal configTimeCacheProviderHolder = new ThreadLocal(); + + private static final ThreadLocal configTimeLobHandlerHolder = new ThreadLocal(); + + /** + * Return the DataSource for the currently configured Hibernate SessionFactory, + * to be used by LocalDataSourceConnectionProvoder. + *
This instance will be set before initialization of the corresponding + * SessionFactory, and reset immediately afterwards. It is thus only available + * during configuration. + * @see #setDataSource + * @see LocalDataSourceConnectionProvider + */ + public static DataSource getConfigTimeDataSource() { + return (DataSource) configTimeDataSourceHolder.get(); + } + + /** + * Return the JTA TransactionManager for the currently configured Hibernate + * SessionFactory, to be used by LocalTransactionManagerLookup. + *
This instance will be set before initialization of the corresponding + * SessionFactory, and reset immediately afterwards. It is thus only available + * during configuration. + * @see #setJtaTransactionManager + * @see LocalTransactionManagerLookup + */ + public static TransactionManager getConfigTimeTransactionManager() { + return (TransactionManager) configTimeTransactionManagerHolder.get(); + } + + /** + * Return the CacheProvider for the currently configured Hibernate SessionFactory, + * to be used by LocalCacheProviderProxy. + *
This instance will be set before initialization of the corresponding + * SessionFactory, and reset immediately afterwards. It is thus only available + * during configuration. + * @see #setCacheProvider + */ + public static CacheProvider getConfigTimeCacheProvider() { + return (CacheProvider) configTimeCacheProviderHolder.get(); + } + + /** + * Return the LobHandler for the currently configured Hibernate SessionFactory, + * to be used by UserType implementations like ClobStringType. + *
This instance will be set before initialization of the corresponding + * SessionFactory, and reset immediately afterwards. It is thus only available + * during configuration. + * @see #setLobHandler + * @see org.springframework.orm.hibernate3.support.ClobStringType + * @see org.springframework.orm.hibernate3.support.BlobByteArrayType + * @see org.springframework.orm.hibernate3.support.BlobSerializableType + */ + public static LobHandler getConfigTimeLobHandler() { + return (LobHandler) configTimeLobHandlerHolder.get(); + } + + + private Class configurationClass = Configuration.class; + + private Resource[] configLocations; + + private String[] mappingResources; + + private Resource[] mappingLocations; + + private Resource[] cacheableMappingLocations; + + private Resource[] mappingJarLocations; + + private Resource[] mappingDirectoryLocations; + + private Properties hibernateProperties; + + private TransactionManager jtaTransactionManager; + + private CacheProvider cacheProvider; + + private LobHandler lobHandler; + + private Interceptor entityInterceptor; + + private NamingStrategy namingStrategy; + + private TypeDefinitionBean[] typeDefinitions; + + private FilterDefinition[] filterDefinitions; + + private Properties entityCacheStrategies; + + private Properties collectionCacheStrategies; + + private Map eventListeners; + + private boolean schemaUpdate = false; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private Configuration configuration; + + + /** + * Specify the Hibernate Configuration class to use. + * Default is "org.hibernate.cfg.Configuration"; any subclass of + * this default Hibernate Configuration class can be specified. + *
Can be set to "org.hibernate.cfg.AnnotationConfiguration" for + * using Hibernate3 annotation support (initially only available as + * alpha download separate from the main Hibernate3 distribution). + *
Annotated packages and annotated classes can be specified via the + * corresponding tags in "hibernate.cfg.xml" then, so this will usually + * be combined with a "configLocation" property that points at such a + * standard Hibernate configuration file. + * @see #setConfigLocation + * @see org.hibernate.cfg.Configuration + * @see org.hibernate.cfg.AnnotationConfiguration + */ + public void setConfigurationClass(Class configurationClass) { + if (configurationClass == null || !Configuration.class.isAssignableFrom(configurationClass)) { + throw new IllegalArgumentException( + "configurationClass must be assignable to [org.hibernate.cfg.Configuration]"); + } + this.configurationClass = configurationClass; + } + + /** + * Set the location of a single Hibernate XML config file, for example as + * classpath resource "classpath:hibernate.cfg.xml". + *
Note: Can be omitted when all necessary properties and mapping + * resources are specified locally via this bean. + * @see org.hibernate.cfg.Configuration#configure(java.net.URL) + */ + public void setConfigLocation(Resource configLocation) { + this.configLocations = new Resource[] {configLocation}; + } + + /** + * Set the locations of multiple Hibernate XML config files, for example as + * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml". + *
Note: Can be omitted when all necessary properties and mapping + * resources are specified locally via this bean. + * @see org.hibernate.cfg.Configuration#configure(java.net.URL) + */ + public void setConfigLocations(Resource[] configLocations) { + this.configLocations = configLocations; + } + + /** + * Set Hibernate mapping resources to be found in the class path, + * like "example.hbm.xml" or "mypackage/example.hbm.xml". + * Analogous to mapping entries in a Hibernate XML config file. + * Alternative to the more generic setMappingLocations method. + *
Can be used to add to mappings from a Hibernate XML config file, + * or to specify all mappings locally. + * @see #setMappingLocations + * @see org.hibernate.cfg.Configuration#addResource + */ + public void setMappingResources(String[] mappingResources) { + this.mappingResources = mappingResources; + } + + /** + * Set locations of Hibernate mapping files, for example as classpath + * resource "classpath:example.hbm.xml". Supports any resource location + * via Spring's resource abstraction, for example relative paths like + * "WEB-INF/mappings/example.hbm.xml" when running in an application context. + *
Can be used to add to mappings from a Hibernate XML config file, + * or to specify all mappings locally. + * @see org.hibernate.cfg.Configuration#addInputStream + */ + public void setMappingLocations(Resource[] mappingLocations) { + this.mappingLocations = mappingLocations; + } + + /** + * Set locations of cacheable Hibernate mapping files, for example as web app + * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location + * via Spring's resource abstraction, as long as the resource can be resolved + * in the file system. + *
Can be used to add to mappings from a Hibernate XML config file, + * or to specify all mappings locally. + * @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File) + */ + public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) { + this.cacheableMappingLocations = cacheableMappingLocations; + } + + /** + * Set locations of jar files that contain Hibernate mapping resources, + * like "WEB-INF/lib/example.hbm.jar". + *
Can be used to add to mappings from a Hibernate XML config file, + * or to specify all mappings locally. + * @see org.hibernate.cfg.Configuration#addJar(java.io.File) + */ + public void setMappingJarLocations(Resource[] mappingJarLocations) { + this.mappingJarLocations = mappingJarLocations; + } + + /** + * Set locations of directories that contain Hibernate mapping resources, + * like "WEB-INF/mappings". + *
Can be used to add to mappings from a Hibernate XML config file, + * or to specify all mappings locally. + * @see org.hibernate.cfg.Configuration#addDirectory(java.io.File) + */ + public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) { + this.mappingDirectoryLocations = mappingDirectoryLocations; + } + + /** + * Set Hibernate properties, such as "hibernate.dialect". + *
Can be used to override values in a Hibernate XML config file, + * or to specify all necessary properties locally. + *
Note: Do not specify a transaction provider here when using + * Spring-driven transactions. It is also advisable to omit connection + * provider settings and use a Spring-set DataSource instead. + * @see #setDataSource + */ + public void setHibernateProperties(Properties hibernateProperties) { + this.hibernateProperties = hibernateProperties; + } + + /** + * Return the Hibernate properties, if any. Mainly available for + * configuration through property paths that specify individual keys. + */ + public Properties getHibernateProperties() { + if (this.hibernateProperties == null) { + this.hibernateProperties = new Properties(); + } + return this.hibernateProperties; + } + + /** + * Set the JTA TransactionManager to be used for Hibernate's + * TransactionManagerLookup. Allows for using a Spring-managed + * JTA TransactionManager for Hibernate's cache synchronization. + *
Note: If this is set, the Hibernate settings should not define a + * transaction manager lookup to avoid meaningless double configuration. + * @see LocalTransactionManagerLookup + */ + public void setJtaTransactionManager(TransactionManager jtaTransactionManager) { + this.jtaTransactionManager = jtaTransactionManager; + } + + /** + * Set the Hibernate CacheProvider to use for the SessionFactory. + * Allows for using a Spring-managed CacheProvider instance. + *
Note: If this is set, the Hibernate settings should not define a + * cache provider to avoid meaningless double configuration. + * @see LocalCacheProviderProxy + */ + public void setCacheProvider(CacheProvider cacheProvider) { + this.cacheProvider = cacheProvider; + } + + /** + * Set the LobHandler to be used by the SessionFactory. + * Will be exposed at config time for UserType implementations. + * @see #getConfigTimeLobHandler + * @see org.hibernate.usertype.UserType + * @see org.springframework.orm.hibernate3.support.ClobStringType + * @see org.springframework.orm.hibernate3.support.BlobByteArrayType + * @see org.springframework.orm.hibernate3.support.BlobSerializableType + */ + public void setLobHandler(LobHandler lobHandler) { + this.lobHandler = lobHandler; + } + + /** + * Set a Hibernate entity interceptor that allows to inspect and change + * property values before writing to and reading from the database. + * Will get applied to any new Session created by this factory. + *
Such an interceptor can either be set at the SessionFactory level, i.e. on + * LocalSessionFactoryBean, or at the Session level, i.e. on HibernateTemplate, + * HibernateInterceptor, and HibernateTransactionManager. It's preferable to set + * it on LocalSessionFactoryBean or HibernateTransactionManager to avoid repeated + * configuration and guarantee consistent behavior in transactions. + * @see HibernateTemplate#setEntityInterceptor + * @see HibernateInterceptor#setEntityInterceptor + * @see HibernateTransactionManager#setEntityInterceptor + * @see org.hibernate.cfg.Configuration#setInterceptor + */ + public void setEntityInterceptor(Interceptor entityInterceptor) { + this.entityInterceptor = entityInterceptor; + } + + /** + * Set a Hibernate NamingStrategy for the SessionFactory, determining the + * physical column and table names given the info in the mapping document. + * @see org.hibernate.cfg.Configuration#setNamingStrategy + */ + public void setNamingStrategy(NamingStrategy namingStrategy) { + this.namingStrategy = namingStrategy; + } + + /** + * Specify the Hibernate type definitions to register with the SessionFactory, + * as Spring TypeDefinitionBean instances. This is an alternative to specifying + * <<typedef> elements in Hibernate mapping files. + *
Unfortunately, Hibernate itself does not define a complete object that + * represents a type definition, hence the need for Spring's TypeDefinitionBean. + * @see TypeDefinitionBean + * @see org.hibernate.cfg.Mappings#addTypeDef(String, String, java.util.Properties) + */ + public void setTypeDefinitions(TypeDefinitionBean[] typeDefinitions) { + this.typeDefinitions = typeDefinitions; + } + + /** + * Specify the Hibernate FilterDefinitions to register with the SessionFactory. + * This is an alternative to specifying <<filter-def> elements in + * Hibernate mapping files. + *
Typically, the passed-in FilterDefinition objects will have been defined + * as Spring FilterDefinitionFactoryBeans, probably as inner beans within the + * LocalSessionFactoryBean definition. + * @see FilterDefinitionFactoryBean + * @see org.hibernate.cfg.Configuration#addFilterDefinition + */ + public void setFilterDefinitions(FilterDefinition[] filterDefinitions) { + this.filterDefinitions = filterDefinitions; + } + + /** + * Specify the cache strategies for entities (persistent classes or named entities). + * This configuration setting corresponds to the <class-cache> entry + * in the "hibernate.cfg.xml" configuration format. + *
For example: + *
+ * <property name="entityCacheStrategies"> + * <props> + * <prop key="com.mycompany.Customer">read-write</prop> + * <prop key="com.mycompany.Product">read-only,myRegion</prop> + * </props> + * </property>+ * Note that appending a cache region name (with a comma separator) is only + * supported on Hibernate 3.1, where this functionality is publically available. + * @param entityCacheStrategies properties that define entity cache strategies, + * with class names as keys and cache concurrency strategies as values + * @see org.hibernate.cfg.Configuration#setCacheConcurrencyStrategy(String, String) + */ + public void setEntityCacheStrategies(Properties entityCacheStrategies) { + this.entityCacheStrategies = entityCacheStrategies; + } + + /** + * Specify the cache strategies for persistent collections (with specific roles). + * This configuration setting corresponds to the <collection-cache> entry + * in the "hibernate.cfg.xml" configuration format. + *
For example: + *
+ * <property name="collectionCacheStrategies"> + * <props> + * <prop key="com.mycompany.Order.items">read-write</prop> + * <prop key="com.mycompany.Product.categories">read-only,myRegion</prop> + * </props> + * </property>+ * Note that appending a cache region name (with a comma separator) is only + * supported on Hibernate 3.1, where this functionality is publically available. + * @param collectionCacheStrategies properties that define collection cache strategies, + * with collection roles as keys and cache concurrency strategies as values + * @see org.hibernate.cfg.Configuration#setCollectionCacheConcurrencyStrategy(String, String) + */ + public void setCollectionCacheStrategies(Properties collectionCacheStrategies) { + this.collectionCacheStrategies = collectionCacheStrategies; + } + + /** + * Specify the Hibernate event listeners to register, with listener types + * as keys and listener objects as values. + *
Instead of a single listener object, you can also pass in a list + * or set of listeners objects as value. However, this is only supported + * on Hibernate 3.1. + *
See the Hibernate documentation for further details on listener types + * and associated listener interfaces. + * @param eventListeners Map with listener type Strings as keys and + * listener objects as values + * @see org.hibernate.cfg.Configuration#setListener(String, Object) + */ + public void setEventListeners(Map eventListeners) { + this.eventListeners = eventListeners; + } + + /** + * Set whether to execute a schema update after SessionFactory initialization. + *
For details on how to make schema update scripts work, see the Hibernate + * documentation, as this class leverages the same schema update script support + * in org.hibernate.cfg.Configuration as Hibernate's own SchemaUpdate tool. + * @see org.hibernate.cfg.Configuration#generateSchemaUpdateScript + * @see org.hibernate.tool.hbm2ddl.SchemaUpdate + */ + public void setSchemaUpdate(boolean schemaUpdate) { + this.schemaUpdate = schemaUpdate; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + protected SessionFactory buildSessionFactory() throws Exception { + // Create Configuration instance. + Configuration config = newConfiguration(); + + DataSource dataSource = getDataSource(); + if (dataSource != null) { + // Make given DataSource available for SessionFactory configuration. + configTimeDataSourceHolder.set(dataSource); + } + if (this.jtaTransactionManager != null) { + // Make Spring-provided JTA TransactionManager available. + configTimeTransactionManagerHolder.set(this.jtaTransactionManager); + } + if (this.cacheProvider != null) { + // Make Spring-provided Hibernate CacheProvider available. + configTimeCacheProviderHolder.set(this.cacheProvider); + } + if (this.lobHandler != null) { + // Make given LobHandler available for SessionFactory configuration. + // Do early because because mapping resource might refer to custom types. + configTimeLobHandlerHolder.set(this.lobHandler); + } + + // Analogous to Hibernate EntityManager's Ejb3Configuration: + // Hibernate doesn't allow setting the bean ClassLoader explicitly, + // so we need to expose it as thread context ClassLoader accordingly. + Thread currentThread = Thread.currentThread(); + ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); + boolean overrideClassLoader = + (this.beanClassLoader != null && !this.beanClassLoader.equals(threadContextClassLoader)); + if (overrideClassLoader) { + currentThread.setContextClassLoader(this.beanClassLoader); + } + + try { + if (isExposeTransactionAwareSessionFactory()) { + // Set Hibernate 3.1 CurrentSessionContext implementation, + // providing the Spring-managed Session as current Session. + // Can be overridden by a custom value for the corresponding Hibernate property. + config.setProperty( + Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName()); + } + + if (this.jtaTransactionManager != null) { + // Set Spring-provided JTA TransactionManager as Hibernate property. + config.setProperty( + Environment.TRANSACTION_STRATEGY, JTATransactionFactory.class.getName()); + config.setProperty( + Environment.TRANSACTION_MANAGER_STRATEGY, LocalTransactionManagerLookup.class.getName()); + } + else { + // Makes the Hibernate Session aware of the presence of a Spring-managed transaction. + // Also sets connection release mode to ON_CLOSE by default. + config.setProperty( + Environment.TRANSACTION_STRATEGY, SpringTransactionFactory.class.getName()); + } + + if (this.entityInterceptor != null) { + // Set given entity interceptor at SessionFactory level. + config.setInterceptor(this.entityInterceptor); + } + + if (this.namingStrategy != null) { + // Pass given naming strategy to Hibernate Configuration. + config.setNamingStrategy(this.namingStrategy); + } + + if (this.typeDefinitions != null) { + // Register specified Hibernate type definitions. + Mappings mappings = config.createMappings(); + for (int i = 0; i < this.typeDefinitions.length; i++) { + TypeDefinitionBean typeDef = this.typeDefinitions[i]; + mappings.addTypeDef(typeDef.getTypeName(), typeDef.getTypeClass(), typeDef.getParameters()); + } + } + + if (this.filterDefinitions != null) { + // Register specified Hibernate FilterDefinitions. + for (int i = 0; i < this.filterDefinitions.length; i++) { + config.addFilterDefinition(this.filterDefinitions[i]); + } + } + + if (this.configLocations != null) { + for (int i = 0; i < this.configLocations.length; i++) { + // Load Hibernate configuration from given location. + config.configure(this.configLocations[i].getURL()); + } + } + + if (this.hibernateProperties != null) { + // Add given Hibernate properties to Configuration. + config.addProperties(this.hibernateProperties); + } + + if (dataSource != null) { + Class providerClass = LocalDataSourceConnectionProvider.class; + if (isUseTransactionAwareDataSource() || dataSource instanceof TransactionAwareDataSourceProxy) { + providerClass = TransactionAwareDataSourceConnectionProvider.class; + } + else if (config.getProperty(Environment.TRANSACTION_MANAGER_STRATEGY) != null) { + providerClass = LocalJtaDataSourceConnectionProvider.class; + } + // Set Spring-provided DataSource as Hibernate ConnectionProvider. + config.setProperty(Environment.CONNECTION_PROVIDER, providerClass.getName()); + } + + if (this.cacheProvider != null) { + // Expose Spring-provided Hibernate CacheProvider. + config.setProperty(Environment.CACHE_PROVIDER, LocalCacheProviderProxy.class.getName()); + } + + if (this.mappingResources != null) { + // Register given Hibernate mapping definitions, contained in resource files. + for (int i = 0; i < this.mappingResources.length; i++) { + Resource resource = new ClassPathResource(this.mappingResources[i].trim(), this.beanClassLoader); + config.addInputStream(resource.getInputStream()); + } + } + + if (this.mappingLocations != null) { + // Register given Hibernate mapping definitions, contained in resource files. + for (int i = 0; i < this.mappingLocations.length; i++) { + config.addInputStream(this.mappingLocations[i].getInputStream()); + } + } + + if (this.cacheableMappingLocations != null) { + // Register given cacheable Hibernate mapping definitions, read from the file system. + for (int i = 0; i < this.cacheableMappingLocations.length; i++) { + config.addCacheableFile(this.cacheableMappingLocations[i].getFile()); + } + } + + if (this.mappingJarLocations != null) { + // Register given Hibernate mapping definitions, contained in jar files. + for (int i = 0; i < this.mappingJarLocations.length; i++) { + Resource resource = this.mappingJarLocations[i]; + config.addJar(resource.getFile()); + } + } + + if (this.mappingDirectoryLocations != null) { + // Register all Hibernate mapping definitions in the given directories. + for (int i = 0; i < this.mappingDirectoryLocations.length; i++) { + File file = this.mappingDirectoryLocations[i].getFile(); + if (!file.isDirectory()) { + throw new IllegalArgumentException( + "Mapping directory location [" + this.mappingDirectoryLocations[i] + + "] does not denote a directory"); + } + config.addDirectory(file); + } + } + + // Tell Hibernate to eagerly compile the mappings that we registered, + // for availability of the mapping information in further processing. + postProcessMappings(config); + config.buildMappings(); + + if (this.entityCacheStrategies != null) { + // Register cache strategies for mapped entities. + for (Enumeration classNames = this.entityCacheStrategies.propertyNames(); classNames.hasMoreElements();) { + String className = (String) classNames.nextElement(); + String[] strategyAndRegion = + StringUtils.commaDelimitedListToStringArray(this.entityCacheStrategies.getProperty(className)); + if (strategyAndRegion.length > 1) { + config.setCacheConcurrencyStrategy(className, strategyAndRegion[0], strategyAndRegion[1]); + } + else if (strategyAndRegion.length > 0) { + config.setCacheConcurrencyStrategy(className, strategyAndRegion[0]); + } + } + } + + if (this.collectionCacheStrategies != null) { + // Register cache strategies for mapped collections. + for (Enumeration collRoles = this.collectionCacheStrategies.propertyNames(); collRoles.hasMoreElements();) { + String collRole = (String) collRoles.nextElement(); + String[] strategyAndRegion = + StringUtils.commaDelimitedListToStringArray(this.collectionCacheStrategies.getProperty(collRole)); + if (strategyAndRegion.length > 1) { + config.setCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0], strategyAndRegion[1]); + } + else if (strategyAndRegion.length > 0) { + config.setCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0]); + } + } + } + + if (this.eventListeners != null) { + // Register specified Hibernate event listeners. + for (Iterator it = this.eventListeners.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Assert.isTrue(entry.getKey() instanceof String, "Event listener key needs to be of type String"); + String listenerType = (String) entry.getKey(); + Object listenerObject = entry.getValue(); + if (listenerObject instanceof Collection) { + Collection listeners = (Collection) listenerObject; + EventListeners listenerRegistry = config.getEventListeners(); + Object[] listenerArray = + (Object[]) Array.newInstance(listenerRegistry.getListenerClassFor(listenerType), listeners.size()); + listenerArray = listeners.toArray(listenerArray); + config.setListeners(listenerType, listenerArray); + } + else { + config.setListener(listenerType, listenerObject); + } + } + } + + // Perform custom post-processing in subclasses. + postProcessConfiguration(config); + + // Build SessionFactory instance. + logger.info("Building new Hibernate SessionFactory"); + this.configuration = config; + return newSessionFactory(config); + } + + finally { + if (dataSource != null) { + // Reset DataSource holder. + configTimeDataSourceHolder.set(null); + } + if (this.jtaTransactionManager != null) { + // Reset TransactionManager holder. + configTimeTransactionManagerHolder.set(null); + } + if (this.cacheProvider != null) { + // Reset CacheProvider holder. + configTimeCacheProviderHolder.set(null); + } + if (this.lobHandler != null) { + // Reset LobHandler holder. + configTimeLobHandlerHolder.set(null); + } + if (overrideClassLoader) { + // Reset original thread context ClassLoader. + currentThread.setContextClassLoader(threadContextClassLoader); + } + } + } + + /** + * Subclasses can override this method to perform custom initialization + * of the Configuration instance used for SessionFactory creation. + * The properties of this LocalSessionFactoryBean will be applied to + * the Configuration object that gets returned here. + *
The default implementation creates a new Configuration instance. + * A custom implementation could prepare the instance in a specific way, + * or use a custom Configuration subclass. + * @return the Configuration instance + * @throws HibernateException in case of Hibernate initialization errors + * @see org.hibernate.cfg.Configuration#Configuration() + */ + protected Configuration newConfiguration() throws HibernateException { + return (Configuration) BeanUtils.instantiateClass(this.configurationClass); + } + + /** + * To be implemented by subclasses that want to to register further mappings + * on the Configuration object after this FactoryBean registered its specified + * mappings. + *
Invoked before the Configuration.buildMappings() call,
+ * so that it can still extend and modify the mapping information.
+ * @param config the current Configuration object
+ * @throws HibernateException in case of Hibernate initialization errors
+ * @see org.hibernate.cfg.Configuration#buildMappings()
+ */
+ protected void postProcessMappings(Configuration config) throws HibernateException {
+ }
+
+ /**
+ * To be implemented by subclasses that want to to perform custom
+ * post-processing of the Configuration object after this FactoryBean
+ * performed its default initialization.
+ *
Invoked after the Configuration.buildMappings() call,
+ * so that it can operate on the completed and fully parsed mapping information.
+ * @param config the current Configuration object
+ * @throws HibernateException in case of Hibernate initialization errors
+ * @see org.hibernate.cfg.Configuration#buildMappings()
+ */
+ protected void postProcessConfiguration(Configuration config) throws HibernateException {
+ }
+
+ /**
+ * Subclasses can override this method to perform custom initialization
+ * of the SessionFactory instance, creating it via the given Configuration
+ * object that got prepared by this LocalSessionFactoryBean.
+ *
The default implementation invokes Configuration's buildSessionFactory. + * A custom implementation could prepare the instance in a specific way, + * or use a custom SessionFactoryImpl subclass. + * @param config Configuration prepared by this LocalSessionFactoryBean + * @return the SessionFactory instance + * @throws HibernateException in case of Hibernate initialization errors + * @see org.hibernate.cfg.Configuration#buildSessionFactory + */ + protected SessionFactory newSessionFactory(Configuration config) throws HibernateException { + return config.buildSessionFactory(); + } + + /** + * Return the Configuration object used to build the SessionFactory. + * Allows access to configuration metadata stored there (rarely needed). + * @throws IllegalStateException if the Configuration object has not been initialized yet + */ + public final Configuration getConfiguration() { + if (this.configuration == null) { + throw new IllegalStateException("Configuration not initialized yet"); + } + return this.configuration; + } + + /** + * Executes schema update if requested. + * @see #setSchemaUpdate + * @see #updateDatabaseSchema() + */ + protected void afterSessionFactoryCreation() throws Exception { + if (this.schemaUpdate) { + DataSource dataSource = getDataSource(); + if (dataSource != null) { + // Make given DataSource available for the schema update, + // which unfortunately reinstantiates a ConnectionProvider. + configTimeDataSourceHolder.set(dataSource); + } + try { + updateDatabaseSchema(); + } + finally { + if (dataSource != null) { + // Reset DataSource holder. + configTimeDataSourceHolder.set(null); + } + } + } + } + + /** + * Allows for schema export on shutdown. + */ + public void destroy() throws HibernateException { + DataSource dataSource = getDataSource(); + if (dataSource != null) { + // Make given DataSource available for potential SchemaExport, + // which unfortunately reinstantiates a ConnectionProvider. + configTimeDataSourceHolder.set(dataSource); + } + try { + super.destroy(); + } + finally { + if (dataSource != null) { + // Reset DataSource holder. + configTimeDataSourceHolder.set(null); + } + } + } + + + /** + * Execute schema drop script, determined by the Configuration object + * used for creating the SessionFactory. A replacement for Hibernate's + * SchemaExport class, to be invoked on application setup. + *
Fetch the LocalSessionFactoryBean itself rather than the exposed
+ * SessionFactory to be able to invoke this method, e.g. via
+ * LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");.
+ *
Uses the SessionFactory that this bean generates for accessing a JDBC + * connection to perform the script. + * @throws org.springframework.dao.DataAccessException in case of script execution errors + * @see org.hibernate.cfg.Configuration#generateDropSchemaScript + * @see org.hibernate.tool.hbm2ddl.SchemaExport#drop + */ + public void dropDatabaseSchema() throws DataAccessException { + logger.info("Dropping database schema for Hibernate SessionFactory"); + HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory()); + hibernateTemplate.execute( + new HibernateCallback() { + public Object doInHibernate(Session session) throws HibernateException, SQLException { + Connection con = session.connection(); + Dialect dialect = Dialect.getDialect(getConfiguration().getProperties()); + String[] sql = getConfiguration().generateDropSchemaScript(dialect); + executeSchemaScript(con, sql); + return null; + } + } + ); + } + + /** + * Execute schema creation script, determined by the Configuration object + * used for creating the SessionFactory. A replacement for Hibernate's + * SchemaExport class, to be invoked on application setup. + *
Fetch the LocalSessionFactoryBean itself rather than the exposed
+ * SessionFactory to be able to invoke this method, e.g. via
+ * LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");.
+ *
Uses the SessionFactory that this bean generates for accessing a JDBC + * connection to perform the script. + * @throws DataAccessException in case of script execution errors + * @see org.hibernate.cfg.Configuration#generateSchemaCreationScript + * @see org.hibernate.tool.hbm2ddl.SchemaExport#create + */ + public void createDatabaseSchema() throws DataAccessException { + logger.info("Creating database schema for Hibernate SessionFactory"); + HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory()); + hibernateTemplate.execute( + new HibernateCallback() { + public Object doInHibernate(Session session) throws HibernateException, SQLException { + Connection con = session.connection(); + Dialect dialect = Dialect.getDialect(getConfiguration().getProperties()); + String[] sql = getConfiguration().generateSchemaCreationScript(dialect); + executeSchemaScript(con, sql); + return null; + } + } + ); + } + + /** + * Execute schema update script, determined by the Configuration object + * used for creating the SessionFactory. A replacement for Hibernate's + * SchemaUpdate class, for automatically executing schema update scripts + * on application startup. Can also be invoked manually. + *
Fetch the LocalSessionFactoryBean itself rather than the exposed
+ * SessionFactory to be able to invoke this method, e.g. via
+ * LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");.
+ *
Uses the SessionFactory that this bean generates for accessing a JDBC + * connection to perform the script. + * @throws DataAccessException in case of script execution errors + * @see #setSchemaUpdate + * @see org.hibernate.cfg.Configuration#generateSchemaUpdateScript + * @see org.hibernate.tool.hbm2ddl.SchemaUpdate + */ + public void updateDatabaseSchema() throws DataAccessException { + logger.info("Updating database schema for Hibernate SessionFactory"); + HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory()); + hibernateTemplate.setFlushMode(HibernateTemplate.FLUSH_NEVER); + hibernateTemplate.execute( + new HibernateCallback() { + public Object doInHibernate(Session session) throws HibernateException, SQLException { + Connection con = session.connection(); + Dialect dialect = Dialect.getDialect(getConfiguration().getProperties()); + DatabaseMetadata metadata = new DatabaseMetadata(con, dialect); + String[] sql = getConfiguration().generateSchemaUpdateScript(dialect, metadata); + executeSchemaScript(con, sql); + return null; + } + } + ); + } + + /** + * Execute the given schema script on the given JDBC Connection. + *
Note that the default implementation will log unsuccessful statements
+ * and continue to execute. Override the executeSchemaStatement
+ * method to treat failures differently.
+ * @param con the JDBC Connection to execute the script on
+ * @param sql the SQL statements to execute
+ * @throws SQLException if thrown by JDBC methods
+ * @see #executeSchemaStatement
+ */
+ protected void executeSchemaScript(Connection con, String[] sql) throws SQLException {
+ if (sql != null && sql.length > 0) {
+ boolean oldAutoCommit = con.getAutoCommit();
+ if (!oldAutoCommit) {
+ con.setAutoCommit(true);
+ }
+ try {
+ Statement stmt = con.createStatement();
+ try {
+ for (int i = 0; i < sql.length; i++) {
+ executeSchemaStatement(stmt, sql[i]);
+ }
+ }
+ finally {
+ JdbcUtils.closeStatement(stmt);
+ }
+ }
+ finally {
+ if (!oldAutoCommit) {
+ con.setAutoCommit(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Execute the given schema SQL on the given JDBC Statement.
+ *
Note that the default implementation will log unsuccessful statements + * and continue to execute. Override this method to treat failures differently. + * @param stmt the JDBC Statement to execute the SQL on + * @param sql the SQL statement to execute + * @throws SQLException if thrown by JDBC methods (and considered fatal) + */ + protected void executeSchemaStatement(Statement stmt, String sql) throws SQLException { + if (logger.isDebugEnabled()) { + logger.debug("Executing schema statement: " + sql); + } + try { + stmt.executeUpdate(sql); + } + catch (SQLException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Unsuccessful schema statement: " + sql, ex); + } + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java new file mode 100644 index 00000000000..0990f8384fc --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import java.util.Properties; + +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; + +import org.hibernate.transaction.TransactionManagerLookup; + +/** + * Implementation of Hibernate's {@link TransactionManagerLookup} interface + * that returns a Spring-managed JTA {@link TransactionManager}, determined + * by LocalSessionFactoryBean's "jtaTransactionManager" property. + * + *
The main advantage of this TransactionManagerLookup is that it avoids + * double configuration of JTA specifics. A single TransactionManager bean can + * be used for both JtaTransactionManager and LocalSessionFactoryBean, with no + * JTA setup in Hibernate configuration. + * + *
Alternatively, use Hibernate's own TransactionManagerLookup implementations: + * Spring's JtaTransactionManager only requires a TransactionManager for suspending + * and resuming transactions, so you might not need to apply such special Spring + * configuration at all. + * + * @author Juergen Hoeller + * @since 1.2 + * @see LocalSessionFactoryBean#setJtaTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ +public class LocalTransactionManagerLookup implements TransactionManagerLookup { + + private final TransactionManager transactionManager; + + + public LocalTransactionManagerLookup() { + TransactionManager tm = LocalSessionFactoryBean.getConfigTimeTransactionManager(); + // absolutely needs thread-bound TransactionManager to initialize + if (tm == null) { + throw new IllegalStateException("No JTA TransactionManager found - " + + "'jtaTransactionManager' property must be set on LocalSessionFactoryBean"); + } + this.transactionManager = tm; + } + + public TransactionManager getTransactionManager(Properties props) { + return this.transactionManager; + } + + public String getUserTransactionName() { + return null; + } + + public Object getTransactionIdentifier(Transaction transaction) { + return transaction; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java new file mode 100644 index 00000000000..83a3d05b73a --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java @@ -0,0 +1,802 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.sql.DataSource; +import javax.transaction.Status; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.Criteria; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Interceptor; +import org.hibernate.JDBCException; +import org.hibernate.NonUniqueResultException; +import org.hibernate.ObjectDeletedException; +import org.hibernate.PersistentObjectException; +import org.hibernate.PropertyValueException; +import org.hibernate.Query; +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.connection.ConnectionProvider; +import org.hibernate.engine.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.springframework.core.NamedThreadLocal; +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; +import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; +import org.springframework.transaction.jta.SpringJtaSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +/** + * Helper class featuring methods for Hibernate Session handling, + * allowing for reuse of Hibernate Session instances within transactions. + * Also provides support for exception translation. + * + *
Supports synchronization with both Spring-managed JTA transactions + * (see {@link org.springframework.transaction.jta.JtaTransactionManager}) + * and non-Spring JTA transactions (i.e. plain JTA or EJB CMT), + * transparently providing transaction-scoped Hibernate Sessions. + * Note that for non-Spring JTA transactions, a JTA TransactionManagerLookup + * has to be specified in the Hibernate configuration. + * + *
Used internally by {@link HibernateTemplate}, {@link HibernateInterceptor}
+ * and {@link HibernateTransactionManager}. Can also be used directly in
+ * application code.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #getSession
+ * @see #releaseSession
+ * @see HibernateTransactionManager
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public abstract class SessionFactoryUtils {
+
+ /**
+ * Order value for TransactionSynchronization objects that clean up Hibernate Sessions.
+ * Returns DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
+ * to execute Session cleanup before JDBC Connection cleanup, if any.
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
+ */
+ public static final int SESSION_SYNCHRONIZATION_ORDER =
+ DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
+
+ static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
+
+ private static final ThreadLocal deferredCloseHolder =
+ new NamedThreadLocal("Hibernate Sessions registered for deferred close");
+
+
+ /**
+ * Determine the DataSource of the given SessionFactory.
+ * @param sessionFactory the SessionFactory to check
+ * @return the DataSource, or null if none found
+ * @see org.hibernate.engine.SessionFactoryImplementor#getConnectionProvider
+ * @see LocalDataSourceConnectionProvider
+ */
+ public static DataSource getDataSource(SessionFactory sessionFactory) {
+ if (sessionFactory instanceof SessionFactoryImplementor) {
+ ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getConnectionProvider();
+ if (cp instanceof LocalDataSourceConnectionProvider) {
+ return ((LocalDataSourceConnectionProvider) cp).getDataSource();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create an appropriate SQLExceptionTranslator for the given SessionFactory.
+ * If a DataSource is found, a SQLErrorCodeSQLExceptionTranslator for the DataSource
+ * is created; else, a SQLStateSQLExceptionTranslator as fallback.
+ * @param sessionFactory the SessionFactory to create the translator for
+ * @return the SQLExceptionTranslator
+ * @see #getDataSource
+ * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
+ * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
+ */
+ public static SQLExceptionTranslator newJdbcExceptionTranslator(SessionFactory sessionFactory) {
+ DataSource ds = getDataSource(sessionFactory);
+ if (ds != null) {
+ return new SQLErrorCodeSQLExceptionTranslator(ds);
+ }
+ return new SQLStateSQLExceptionTranslator();
+ }
+
+ /**
+ * Try to retrieve the JTA TransactionManager from the given SessionFactory
+ * and/or Session. Check the passed-in SessionFactory for implementing
+ * SessionFactoryImplementor (the usual case), falling back to the
+ * SessionFactory reference that the Session itself carries.
+ * @param sessionFactory Hibernate SessionFactory
+ * @param session Hibernate Session (can also be null)
+ * @return the JTA TransactionManager, if any
+ * @see javax.transaction.TransactionManager
+ * @see SessionFactoryImplementor#getTransactionManager
+ * @see Session#getSessionFactory
+ * @see org.hibernate.impl.SessionFactoryImpl
+ */
+ public static TransactionManager getJtaTransactionManager(SessionFactory sessionFactory, Session session) {
+ SessionFactoryImplementor sessionFactoryImpl = null;
+ if (sessionFactory instanceof SessionFactoryImplementor) {
+ sessionFactoryImpl = ((SessionFactoryImplementor) sessionFactory);
+ }
+ else if (session != null) {
+ SessionFactory internalFactory = session.getSessionFactory();
+ if (internalFactory instanceof SessionFactoryImplementor) {
+ sessionFactoryImpl = (SessionFactoryImplementor) internalFactory;
+ }
+ }
+ return (sessionFactoryImpl != null ? sessionFactoryImpl.getTransactionManager() : null);
+ }
+
+
+ /**
+ * Get a Hibernate Session for the given SessionFactory. Is aware of and will
+ * return any existing corresponding Session bound to the current thread, for
+ * example when using {@link HibernateTransactionManager}. Will create a new
+ * Session otherwise, if "allowCreate" is true.
+ *
This is the getSession method used by typical data access code,
+ * in combination with releaseSession called when done with
+ * the Session. Note that HibernateTemplate allows to write data access code
+ * without caring about such resource handling.
+ * @param sessionFactory Hibernate SessionFactory to create the session with
+ * @param allowCreate whether a non-transactional Session should be created
+ * when no transactional Session can be found for the current thread
+ * @return the Hibernate Session
+ * @throws DataAccessResourceFailureException if the Session couldn't be created
+ * @throws IllegalStateException if no thread-bound Session found and
+ * "allowCreate" is false
+ * @see #getSession(SessionFactory, Interceptor, SQLExceptionTranslator)
+ * @see #releaseSession
+ * @see HibernateTemplate
+ */
+ public static Session getSession(SessionFactory sessionFactory, boolean allowCreate)
+ throws DataAccessResourceFailureException, IllegalStateException {
+
+ try {
+ return doGetSession(sessionFactory, null, null, allowCreate);
+ }
+ catch (HibernateException ex) {
+ throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
+ }
+ }
+
+ /**
+ * Get a Hibernate Session for the given SessionFactory. Is aware of and will
+ * return any existing corresponding Session bound to the current thread, for
+ * example when using {@link HibernateTransactionManager}. Will always create
+ * a new Session otherwise.
+ *
Supports setting a Session-level Hibernate entity interceptor that allows
+ * to inspect and change property values before writing to and reading from the
+ * database. Such an interceptor can also be set at the SessionFactory level
+ * (i.e. on LocalSessionFactoryBean), on HibernateTransactionManager, or on
+ * HibernateInterceptor/HibernateTemplate.
+ * @param sessionFactory Hibernate SessionFactory to create the session with
+ * @param entityInterceptor Hibernate entity interceptor, or null if none
+ * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
+ * Session on transaction synchronization (may be null; only used
+ * when actually registering a transaction synchronization)
+ * @return the Hibernate Session
+ * @throws DataAccessResourceFailureException if the Session couldn't be created
+ * @see LocalSessionFactoryBean#setEntityInterceptor
+ * @see HibernateInterceptor#setEntityInterceptor
+ * @see HibernateTemplate#setEntityInterceptor
+ */
+ public static Session getSession(
+ SessionFactory sessionFactory, Interceptor entityInterceptor,
+ SQLExceptionTranslator jdbcExceptionTranslator) throws DataAccessResourceFailureException {
+
+ try {
+ return doGetSession(sessionFactory, entityInterceptor, jdbcExceptionTranslator, true);
+ }
+ catch (HibernateException ex) {
+ throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
+ }
+ }
+
+ /**
+ * Get a Hibernate Session for the given SessionFactory. Is aware of and will
+ * return any existing corresponding Session bound to the current thread, for
+ * example when using {@link HibernateTransactionManager}. Will create a new
+ * Session otherwise, if "allowCreate" is true.
+ *
Throws the original HibernateException, in contrast to {@link #getSession}.
+ * @param sessionFactory Hibernate SessionFactory to create the session with
+ * @param allowCreate whether a non-transactional Session should be created
+ * when no transactional Session can be found for the current thread
+ * @return the Hibernate Session
+ * @throws HibernateException if the Session couldn't be created
+ * @throws IllegalStateException if no thread-bound Session found and allowCreate false
+ */
+ public static Session doGetSession(SessionFactory sessionFactory, boolean allowCreate)
+ throws HibernateException, IllegalStateException {
+
+ return doGetSession(sessionFactory, null, null, allowCreate);
+ }
+
+ /**
+ * Get a Hibernate Session for the given SessionFactory. Is aware of and will
+ * return any existing corresponding Session bound to the current thread, for
+ * example when using {@link HibernateTransactionManager}. Will create a new
+ * Session otherwise, if "allowCreate" is true.
+ *
Same as {@link #getSession}, but throwing the original HibernateException.
+ * @param sessionFactory Hibernate SessionFactory to create the session with
+ * @param entityInterceptor Hibernate entity interceptor, or null if none
+ * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
+ * Session on transaction synchronization (may be null)
+ * @param allowCreate whether a non-transactional Session should be created
+ * when no transactional Session can be found for the current thread
+ * @return the Hibernate Session
+ * @throws HibernateException if the Session couldn't be created
+ * @throws IllegalStateException if no thread-bound Session found and
+ * "allowCreate" is false
+ */
+ private static Session doGetSession(
+ SessionFactory sessionFactory, Interceptor entityInterceptor,
+ SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)
+ throws HibernateException, IllegalStateException {
+
+ Assert.notNull(sessionFactory, "No SessionFactory specified");
+
+ SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ if (sessionHolder != null && !sessionHolder.isEmpty()) {
+ // pre-bound Hibernate Session
+ Session session = null;
+ if (TransactionSynchronizationManager.isSynchronizationActive() &&
+ sessionHolder.doesNotHoldNonDefaultSession()) {
+ // Spring transaction management is active ->
+ // register pre-bound Session with it for transactional flushing.
+ session = sessionHolder.getValidatedSession();
+ if (session != null && !sessionHolder.isSynchronizedWithTransaction()) {
+ logger.debug("Registering Spring transaction synchronization for existing Hibernate Session");
+ TransactionSynchronizationManager.registerSynchronization(
+ new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false));
+ sessionHolder.setSynchronizedWithTransaction(true);
+ // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
+ // with FlushMode.NEVER, which needs to allow flushing within the transaction.
+ FlushMode flushMode = session.getFlushMode();
+ if (flushMode.lessThan(FlushMode.COMMIT) &&
+ !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
+ session.setFlushMode(FlushMode.AUTO);
+ sessionHolder.setPreviousFlushMode(flushMode);
+ }
+ }
+ }
+ else {
+ // No Spring transaction management active -> try JTA transaction synchronization.
+ session = getJtaSynchronizedSession(sessionHolder, sessionFactory, jdbcExceptionTranslator);
+ }
+ if (session != null) {
+ return session;
+ }
+ }
+
+ logger.debug("Opening Hibernate Session");
+ Session session = (entityInterceptor != null ?
+ sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession());
+
+ // Use same Session for further Hibernate actions within the transaction.
+ // Thread object will get removed by synchronization at transaction completion.
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ // We're within a Spring-managed transaction, possibly from JtaTransactionManager.
+ logger.debug("Registering Spring transaction synchronization for new Hibernate Session");
+ SessionHolder holderToUse = sessionHolder;
+ if (holderToUse == null) {
+ holderToUse = new SessionHolder(session);
+ }
+ else {
+ holderToUse.addSession(session);
+ }
+ if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
+ session.setFlushMode(FlushMode.NEVER);
+ }
+ TransactionSynchronizationManager.registerSynchronization(
+ new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true));
+ holderToUse.setSynchronizedWithTransaction(true);
+ if (holderToUse != sessionHolder) {
+ TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse);
+ }
+ }
+ else {
+ // No Spring transaction management active -> try JTA transaction synchronization.
+ registerJtaSynchronization(session, sessionFactory, jdbcExceptionTranslator, sessionHolder);
+ }
+
+ // Check whether we are allowed to return the Session.
+ if (!allowCreate && !isSessionTransactional(session, sessionFactory)) {
+ closeSession(session);
+ throw new IllegalStateException("No Hibernate Session bound to thread, " +
+ "and configuration does not allow creation of non-transactional one here");
+ }
+
+ return session;
+ }
+
+ /**
+ * Retrieve a Session from the given SessionHolder, potentially from a
+ * JTA transaction synchronization.
+ * @param sessionHolder the SessionHolder to check
+ * @param sessionFactory the SessionFactory to get the JTA TransactionManager from
+ * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
+ * Session on transaction synchronization (may be null)
+ * @return the associated Session, if any
+ * @throws DataAccessResourceFailureException if the Session couldn't be created
+ */
+ private static Session getJtaSynchronizedSession(
+ SessionHolder sessionHolder, SessionFactory sessionFactory,
+ SQLExceptionTranslator jdbcExceptionTranslator) throws DataAccessResourceFailureException {
+
+ // JTA synchronization is only possible with a javax.transaction.TransactionManager.
+ // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified
+ // in Hibernate configuration, it will contain a TransactionManager reference.
+ TransactionManager jtaTm = getJtaTransactionManager(sessionFactory, sessionHolder.getAnySession());
+ if (jtaTm != null) {
+ // Check whether JTA transaction management is active ->
+ // fetch pre-bound Session for the current JTA transaction, if any.
+ // (just necessary for JTA transaction suspension, with an individual
+ // Hibernate Session per currently active/suspended transaction)
+ try {
+ // Look for transaction-specific Session.
+ Transaction jtaTx = jtaTm.getTransaction();
+ if (jtaTx != null) {
+ int jtaStatus = jtaTx.getStatus();
+ if (jtaStatus == Status.STATUS_ACTIVE || jtaStatus == Status.STATUS_MARKED_ROLLBACK) {
+ Session session = sessionHolder.getValidatedSession(jtaTx);
+ if (session == null && !sessionHolder.isSynchronizedWithTransaction()) {
+ // No transaction-specific Session found: If not already marked as
+ // synchronized with transaction, register the default thread-bound
+ // Session as JTA-transactional. If there is no default Session,
+ // we're a new inner JTA transaction with an outer one being suspended:
+ // In that case, we'll return null to trigger opening of a new Session.
+ session = sessionHolder.getValidatedSession();
+ if (session != null) {
+ logger.debug("Registering JTA transaction synchronization for existing Hibernate Session");
+ sessionHolder.addSession(jtaTx, session);
+ jtaTx.registerSynchronization(
+ new SpringJtaSynchronizationAdapter(
+ new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false),
+ jtaTm));
+ sessionHolder.setSynchronizedWithTransaction(true);
+ // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
+ // with FlushMode.NEVER, which needs to allow flushing within the transaction.
+ FlushMode flushMode = session.getFlushMode();
+ if (flushMode.lessThan(FlushMode.COMMIT)) {
+ session.setFlushMode(FlushMode.AUTO);
+ sessionHolder.setPreviousFlushMode(flushMode);
+ }
+ }
+ }
+ return session;
+ }
+ }
+ // No transaction active -> simply return default thread-bound Session, if any
+ // (possibly from OpenSessionInViewFilter/Interceptor).
+ return sessionHolder.getValidatedSession();
+ }
+ catch (Throwable ex) {
+ throw new DataAccessResourceFailureException("Could not check JTA transaction", ex);
+ }
+ }
+ else {
+ // No JTA TransactionManager -> simply return default thread-bound Session, if any
+ // (possibly from OpenSessionInViewFilter/Interceptor).
+ return sessionHolder.getValidatedSession();
+ }
+ }
+
+ /**
+ * Register a JTA synchronization for the given Session, if any.
+ * @param sessionHolder the existing thread-bound SessionHolder, if any
+ * @param session the Session to register
+ * @param sessionFactory the SessionFactory that the Session was created with
+ * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
+ * Session on transaction synchronization (may be null)
+ */
+ private static void registerJtaSynchronization(Session session, SessionFactory sessionFactory,
+ SQLExceptionTranslator jdbcExceptionTranslator, SessionHolder sessionHolder) {
+
+ // JTA synchronization is only possible with a javax.transaction.TransactionManager.
+ // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified
+ // in Hibernate configuration, it will contain a TransactionManager reference.
+ TransactionManager jtaTm = getJtaTransactionManager(sessionFactory, session);
+ if (jtaTm != null) {
+ try {
+ Transaction jtaTx = jtaTm.getTransaction();
+ if (jtaTx != null) {
+ int jtaStatus = jtaTx.getStatus();
+ if (jtaStatus == Status.STATUS_ACTIVE || jtaStatus == Status.STATUS_MARKED_ROLLBACK) {
+ logger.debug("Registering JTA transaction synchronization for new Hibernate Session");
+ SessionHolder holderToUse = sessionHolder;
+ // Register JTA Transaction with existing SessionHolder.
+ // Create a new SessionHolder if none existed before.
+ if (holderToUse == null) {
+ holderToUse = new SessionHolder(jtaTx, session);
+ }
+ else {
+ holderToUse.addSession(jtaTx, session);
+ }
+ jtaTx.registerSynchronization(
+ new SpringJtaSynchronizationAdapter(
+ new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true),
+ jtaTm));
+ holderToUse.setSynchronizedWithTransaction(true);
+ if (holderToUse != sessionHolder) {
+ TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse);
+ }
+ }
+ }
+ }
+ catch (Throwable ex) {
+ throw new DataAccessResourceFailureException(
+ "Could not register synchronization with JTA TransactionManager", ex);
+ }
+ }
+ }
+
+
+ /**
+ * Get a new Hibernate Session from the given SessionFactory.
+ * Will return a new Session even if there already is a pre-bound
+ * Session for the given SessionFactory.
+ *
Within a transaction, this method will create a new Session + * that shares the transaction's JDBC Connection. More specifically, + * it will use the same JDBC Connection as the pre-bound Hibernate Session. + * @param sessionFactory Hibernate SessionFactory to create the session with + * @return the new Session + */ + public static Session getNewSession(SessionFactory sessionFactory) { + return getNewSession(sessionFactory, null); + } + + /** + * Get a new Hibernate Session from the given SessionFactory. + * Will return a new Session even if there already is a pre-bound + * Session for the given SessionFactory. + *
Within a transaction, this method will create a new Session
+ * that shares the transaction's JDBC Connection. More specifically,
+ * it will use the same JDBC Connection as the pre-bound Hibernate Session.
+ * @param sessionFactory Hibernate SessionFactory to create the session with
+ * @param entityInterceptor Hibernate entity interceptor, or null if none
+ * @return the new Session
+ */
+ public static Session getNewSession(SessionFactory sessionFactory, Interceptor entityInterceptor) {
+ Assert.notNull(sessionFactory, "No SessionFactory specified");
+
+ try {
+ SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ if (sessionHolder != null && !sessionHolder.isEmpty()) {
+ if (entityInterceptor != null) {
+ return sessionFactory.openSession(sessionHolder.getAnySession().connection(), entityInterceptor);
+ }
+ else {
+ return sessionFactory.openSession(sessionHolder.getAnySession().connection());
+ }
+ }
+ else {
+ if (entityInterceptor != null) {
+ return sessionFactory.openSession(entityInterceptor);
+ }
+ else {
+ return sessionFactory.openSession();
+ }
+ }
+ }
+ catch (HibernateException ex) {
+ throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
+ }
+ }
+
+
+ /**
+ * Stringify the given Session for debug logging.
+ * Returns output equivalent to Object.toString():
+ * the fully qualified class name + "@" + the identity hash code.
+ *
The sole reason why this is necessary is because Hibernate3's
+ * Session.toString() implementation is broken (and won't be fixed):
+ * it logs the toString representation of all persistent objects in the Session,
+ * which might lead to ConcurrentModificationExceptions if the persistent objects
+ * in turn refer to the Session (for example, for lazy loading).
+ * @param session the Hibernate Session to stringify
+ * @return the String representation of the given Session
+ */
+ public static String toString(Session session) {
+ return session.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(session));
+ }
+
+ /**
+ * Return whether there is a transactional Hibernate Session for the current thread,
+ * that is, a Session bound to the current thread by Spring's transaction facilities.
+ * @param sessionFactory Hibernate SessionFactory to check (may be null)
+ * @return whether there is a transactional Session for current thread
+ */
+ public static boolean hasTransactionalSession(SessionFactory sessionFactory) {
+ if (sessionFactory == null) {
+ return false;
+ }
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ return (sessionHolder != null && !sessionHolder.isEmpty());
+ }
+
+ /**
+ * Return whether the given Hibernate Session is transactional, that is,
+ * bound to the current thread by Spring's transaction facilities.
+ * @param session the Hibernate Session to check
+ * @param sessionFactory Hibernate SessionFactory that the Session was created with
+ * (may be null)
+ * @return whether the Session is transactional
+ */
+ public static boolean isSessionTransactional(Session session, SessionFactory sessionFactory) {
+ if (sessionFactory == null) {
+ return false;
+ }
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ return (sessionHolder != null && sessionHolder.containsSession(session));
+ }
+
+ /**
+ * Apply the current transaction timeout, if any, to the given
+ * Hibernate Query object.
+ * @param query the Hibernate Query object
+ * @param sessionFactory Hibernate SessionFactory that the Query was created for
+ * (may be null)
+ * @see org.hibernate.Query#setTimeout
+ */
+ public static void applyTransactionTimeout(Query query, SessionFactory sessionFactory) {
+ Assert.notNull(query, "No Query object specified");
+ if (sessionFactory != null) {
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ if (sessionHolder != null && sessionHolder.hasTimeout()) {
+ query.setTimeout(sessionHolder.getTimeToLiveInSeconds());
+ }
+ }
+ }
+
+ /**
+ * Apply the current transaction timeout, if any, to the given
+ * Hibernate Criteria object.
+ * @param criteria the Hibernate Criteria object
+ * @param sessionFactory Hibernate SessionFactory that the Criteria was created for
+ * @see org.hibernate.Criteria#setTimeout
+ */
+ public static void applyTransactionTimeout(Criteria criteria, SessionFactory sessionFactory) {
+ Assert.notNull(criteria, "No Criteria object specified");
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ if (sessionHolder != null && sessionHolder.hasTimeout()) {
+ criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds());
+ }
+ }
+
+ /**
+ * Convert the given HibernateException to an appropriate exception
+ * from the org.springframework.dao hierarchy.
+ * @param ex HibernateException that occured
+ * @return the corresponding DataAccessException instance
+ * @see HibernateAccessor#convertHibernateAccessException
+ * @see HibernateTransactionManager#convertHibernateAccessException
+ */
+ public static DataAccessException convertHibernateAccessException(HibernateException ex) {
+ if (ex instanceof JDBCConnectionException) {
+ return new DataAccessResourceFailureException(ex.getMessage(), ex);
+ }
+ if (ex instanceof SQLGrammarException) {
+ return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
+ }
+ if (ex instanceof LockAcquisitionException) {
+ return new CannotAcquireLockException(ex.getMessage(), ex);
+ }
+ if (ex instanceof ConstraintViolationException) {
+ return new DataIntegrityViolationException(ex.getMessage(), ex);
+ }
+ if (ex instanceof DataException) {
+ return new DataIntegrityViolationException(ex.getMessage(), 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);
+ }
+ if (ex instanceof StaleObjectStateException) {
+ return new HibernateOptimisticLockingFailureException((StaleObjectStateException) ex);
+ }
+ if (ex instanceof StaleStateException) {
+ return new HibernateOptimisticLockingFailureException((StaleStateException) ex);
+ }
+
+ // fallback
+ return new HibernateSystemException(ex);
+ }
+
+
+ /**
+ * Determine whether deferred close is active for the current thread
+ * and the given SessionFactory.
+ * @param sessionFactory the Hibernate SessionFactory to check
+ * @return whether deferred close is active
+ */
+ public static boolean isDeferredCloseActive(SessionFactory sessionFactory) {
+ Assert.notNull(sessionFactory, "No SessionFactory specified");
+ Map holderMap = (Map) deferredCloseHolder.get();
+ return (holderMap != null && holderMap.containsKey(sessionFactory));
+ }
+
+ /**
+ * Initialize deferred close for the current thread and the given SessionFactory.
+ * Sessions will not be actually closed on close calls then, but rather at a
+ * {@link #processDeferredClose} call at a finishing point (like request completion).
+ *
Used by {@link org.springframework.orm.hibernate3.support.OpenSessionInViewFilter}
+ * and {@link org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor}
+ * when not configured for a single session.
+ * @param sessionFactory the Hibernate SessionFactory to initialize deferred close for
+ * @see #processDeferredClose
+ * @see #releaseSession
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter#setSingleSession
+ * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor#setSingleSession
+ */
+ public static void initDeferredClose(SessionFactory sessionFactory) {
+ Assert.notNull(sessionFactory, "No SessionFactory specified");
+ logger.debug("Initializing deferred close of Hibernate Sessions");
+ Map holderMap = (Map) deferredCloseHolder.get();
+ if (holderMap == null) {
+ holderMap = new HashMap();
+ deferredCloseHolder.set(holderMap);
+ }
+ holderMap.put(sessionFactory, new LinkedHashSet(4));
+ }
+
+ /**
+ * Process all Hibernate Sessions that have been registered for deferred close
+ * for the given SessionFactory.
+ * @param sessionFactory the Hibernate SessionFactory to process deferred close for
+ * @see #initDeferredClose
+ * @see #releaseSession
+ */
+ public static void processDeferredClose(SessionFactory sessionFactory) {
+ Assert.notNull(sessionFactory, "No SessionFactory specified");
+
+ Map holderMap = (Map) deferredCloseHolder.get();
+ if (holderMap == null || !holderMap.containsKey(sessionFactory)) {
+ throw new IllegalStateException("Deferred close not active for SessionFactory [" + sessionFactory + "]");
+ }
+
+ logger.debug("Processing deferred close of Hibernate Sessions");
+ Set sessions = (Set) holderMap.remove(sessionFactory);
+ for (Iterator it = sessions.iterator(); it.hasNext();) {
+ closeSession((Session) it.next());
+ }
+
+ if (holderMap.isEmpty()) {
+ deferredCloseHolder.set(null);
+ }
+ }
+
+ /**
+ * Close the given Session, created via the given factory,
+ * if it is not managed externally (i.e. not bound to the thread).
+ * @param session the Hibernate Session to close (may be null)
+ * @param sessionFactory Hibernate SessionFactory that the Session was created with
+ * (may be null)
+ */
+ public static void releaseSession(Session session, SessionFactory sessionFactory) {
+ if (session == null) {
+ return;
+ }
+ // Only close non-transactional Sessions.
+ if (!isSessionTransactional(session, sessionFactory)) {
+ closeSessionOrRegisterDeferredClose(session, sessionFactory);
+ }
+ }
+
+ /**
+ * Close the given Session or register it for deferred close.
+ * @param session the Hibernate Session to close
+ * @param sessionFactory Hibernate SessionFactory that the Session was created with
+ * (may be null)
+ * @see #initDeferredClose
+ * @see #processDeferredClose
+ */
+ static void closeSessionOrRegisterDeferredClose(Session session, SessionFactory sessionFactory) {
+ Map holderMap = (Map) deferredCloseHolder.get();
+ if (holderMap != null && sessionFactory != null && holderMap.containsKey(sessionFactory)) {
+ logger.debug("Registering Hibernate Session for deferred close");
+ // Switch Session to FlushMode.NEVER for remaining lifetime.
+ session.setFlushMode(FlushMode.NEVER);
+ Set sessions = (Set) holderMap.get(sessionFactory);
+ sessions.add(session);
+ }
+ else {
+ closeSession(session);
+ }
+ }
+
+ /**
+ * Perform actual closing of the Hibernate Session,
+ * catching and logging any cleanup exceptions thrown.
+ * @param session the Hibernate Session to close (may be null)
+ * @see org.hibernate.Session#close()
+ */
+ public static void closeSession(Session session) {
+ if (session != null) {
+ logger.debug("Closing Hibernate Session");
+ 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);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java
new file mode 100644
index 00000000000..b526ff38e32
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.hibernate.FlushMode;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+
+import org.springframework.transaction.support.ResourceHolderSupport;
+import org.springframework.util.Assert;
+
+/**
+ * Session holder, wrapping a Hibernate Session and a Hibernate Transaction.
+ * HibernateTransactionManager binds instances of this class to the thread,
+ * for a given SessionFactory.
+ *
+ *
Note: This is an SPI class, not intended to be used by applications. + * + * @author Juergen Hoeller + * @since 1.2 + * @see HibernateTransactionManager + * @see SessionFactoryUtils + */ +public class SessionHolder extends ResourceHolderSupport { + + private static final Object DEFAULT_KEY = new Object(); + + /** + * This Map needs to be synchronized because there might be multi-threaded + * access in the case of JTA with remote transaction propagation. + */ + private final Map sessionMap = Collections.synchronizedMap(new HashMap(1)); + + private Transaction transaction; + + private FlushMode previousFlushMode; + + + public SessionHolder(Session session) { + addSession(session); + } + + public SessionHolder(Object key, Session session) { + addSession(key, session); + } + + + public Session getSession() { + return getSession(DEFAULT_KEY); + } + + public Session getSession(Object key) { + return (Session) this.sessionMap.get(key); + } + + public Session getValidatedSession() { + return getValidatedSession(DEFAULT_KEY); + } + + public Session getValidatedSession(Object key) { + Session session = (Session) this.sessionMap.get(key); + // Check for dangling Session that's around but already closed. + // Effectively an assertion: that should never happen in practice. + // We'll seamlessly remove the Session here, to not let it cause + // any side effects. + if (session != null && !session.isOpen()) { + this.sessionMap.remove(key); + session = null; + } + return session; + } + + public Session getAnySession() { + synchronized (this.sessionMap) { + if (!this.sessionMap.isEmpty()) { + return (Session) this.sessionMap.values().iterator().next(); + } + return null; + } + } + + public void addSession(Session session) { + addSession(DEFAULT_KEY, session); + } + + public void addSession(Object key, Session session) { + Assert.notNull(key, "Key must not be null"); + Assert.notNull(session, "Session must not be null"); + this.sessionMap.put(key, session); + } + + public Session removeSession(Object key) { + return (Session) this.sessionMap.remove(key); + } + + public boolean containsSession(Session session) { + return this.sessionMap.containsValue(session); + } + + public boolean isEmpty() { + return this.sessionMap.isEmpty(); + } + + public boolean doesNotHoldNonDefaultSession() { + synchronized (this.sessionMap) { + return this.sessionMap.isEmpty() || + (this.sessionMap.size() == 1 && this.sessionMap.containsKey(DEFAULT_KEY)); + } + } + + + 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; + } + + + public void clear() { + super.clear(); + this.transaction = null; + this.previousFlushMode = null; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java new file mode 100644 index 00000000000..e914c19fe4b --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import org.hibernate.HibernateException; +import org.hibernate.classic.Session; +import org.hibernate.context.CurrentSessionContext; +import org.hibernate.engine.SessionFactoryImplementor; + +/** + * Implementation of Hibernate 3.1's CurrentSessionContext interface + * that delegates to Spring's SessionFactoryUtils for providing a + * Spring-managed current Session. + * + *
Used by Spring's {@link LocalSessionFactoryBean} when told to expose a + * transaction-aware SessionFactory. This is the default as of Spring 2.5. + * + *
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 2.0 + * @see SessionFactoryUtils#doGetSession + * @see LocalSessionFactoryBean#setExposeTransactionAwareSessionFactory + */ +public class SpringSessionContext implements CurrentSessionContext { + + private final SessionFactoryImplementor sessionFactory; + + + /** + * 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; + } + + + /** + * Retrieve the Spring-managed Session for the current thread, if any. + */ + public Session currentSession() throws HibernateException { + try { + return (org.hibernate.classic.Session) SessionFactoryUtils.doGetSession(this.sessionFactory, false); + } + catch (IllegalStateException ex) { + throw new HibernateException(ex.getMessage()); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java new file mode 100644 index 00000000000..3b58cd7cf21 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java @@ -0,0 +1,236 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; + +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.engine.SessionImplementor; + +import org.springframework.core.Ordered; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Callback for resource cleanup at the end of a Spring-managed JTA transaction, + * that is, when participating in a JtaTransactionManager transaction. + * + * @author Juergen Hoeller + * @since 1.2 + * @see SessionFactoryUtils + * @see org.springframework.transaction.jta.JtaTransactionManager + */ +class SpringSessionSynchronization implements TransactionSynchronization, Ordered { + + private final SessionHolder sessionHolder; + + private final SessionFactory sessionFactory; + + private final SQLExceptionTranslator jdbcExceptionTranslator; + + private final boolean newSession; + + /** + * Whether Hibernate has a looked-up JTA TransactionManager that it will + * automatically register CacheSynchronizations with on Session connect. + */ + private boolean hibernateTransactionCompletion = false; + + private Transaction jtaTransaction; + + private boolean holderActive = true; + + + public SpringSessionSynchronization( + SessionHolder sessionHolder, SessionFactory sessionFactory, + SQLExceptionTranslator jdbcExceptionTranslator, boolean newSession) { + + this.sessionHolder = sessionHolder; + this.sessionFactory = sessionFactory; + this.jdbcExceptionTranslator = jdbcExceptionTranslator; + this.newSession = newSession; + + // Check whether the SessionFactory has a JTA TransactionManager. + TransactionManager jtaTm = + SessionFactoryUtils.getJtaTransactionManager(sessionFactory, sessionHolder.getAnySession()); + if (jtaTm != null) { + this.hibernateTransactionCompletion = true; + // Fetch current JTA Transaction object + // (just necessary for JTA transaction suspension, with an individual + // Hibernate Session per currently active/suspended transaction). + try { + this.jtaTransaction = jtaTm.getTransaction(); + } + catch (SystemException ex) { + throw new DataAccessResourceFailureException("Could not access JTA transaction", ex); + } + } + } + + /** + * Check whether there is a Hibernate Session for the current JTA + * transaction. Else, fall back to the default thread-bound Session. + */ + private Session getCurrentSession() { + Session session = null; + if (this.jtaTransaction != null) { + session = this.sessionHolder.getSession(this.jtaTransaction); + } + if (session == null) { + session = this.sessionHolder.getSession(); + } + return session; + } + + + 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 beforeCommit(boolean readOnly) throws DataAccessException { + if (!readOnly) { + Session session = getCurrentSession(); + // Read-write transaction -> flush the Hibernate Session. + // Further check: only flush when not FlushMode.NEVER/MANUAL. + if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) { + try { + SessionFactoryUtils.logger.debug("Flushing Hibernate Session on transaction synchronization"); + session.flush(); + } + catch (HibernateException ex) { + if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) { + JDBCException jdbcEx = (JDBCException) ex; + throw this.jdbcExceptionTranslator.translate( + "Hibernate flushing: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException()); + } + throw SessionFactoryUtils.convertHibernateAccessException(ex); + } + } + } + } + + public void beforeCompletion() { + if (this.jtaTransaction != null) { + // Typically in case of a suspended JTA transaction: + // Remove the Session for the current JTA transaction, but keep the holder. + Session session = this.sessionHolder.removeSession(this.jtaTransaction); + if (session != null) { + if (this.sessionHolder.isEmpty()) { + // No Sessions for JTA transactions bound anymore -> could remove it. + TransactionSynchronizationManager.unbindResourceIfPossible(this.sessionFactory); + this.holderActive = false; + } + // Do not close a pre-bound Session. In that case, we'll find the + // transaction-specific Session the same as the default Session. + if (session != this.sessionHolder.getSession()) { + SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, this.sessionFactory); + } + else { + 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(); + } + return; + } + } + // We'll only get here if there was no specific JTA transaction to handle. + if (this.newSession) { + // Default behavior: unbind and close the thread-bound Hibernate Session. + TransactionSynchronizationManager.unbindResource(this.sessionFactory); + this.holderActive = false; + if (this.hibernateTransactionCompletion) { + // Close the Hibernate Session here in case of a Hibernate TransactionManagerLookup: + // Hibernate will automatically defer the actual closing until JTA transaction completion. + // Else, the Session will be closed in the afterCompletion method, to provide the + // correct transaction status for releasing the Session's cache locks. + SessionFactoryUtils.closeSessionOrRegisterDeferredClose(this.sessionHolder.getSession(), this.sessionFactory); + } + } + else { + 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()); + } + if (this.hibernateTransactionCompletion) { + // Eagerly disconnect the Session here, to make release mode "on_close" work nicely. + // We know that this is appropriate if a TransactionManagerLookup has been specified. + session.disconnect(); + } + } + } + + public void afterCommit() { + } + + public void afterCompletion(int status) { + if (!this.hibernateTransactionCompletion || !this.newSession) { + // No Hibernate TransactionManagerLookup: apply afterTransactionCompletion callback. + // Always perform explicit afterTransactionCompletion callback for pre-bound Session, + // even with Hibernate TransactionManagerLookup (which only applies to new Sessions). + Session session = this.sessionHolder.getSession(); + // Provide correct transaction status for releasing the Session's cache locks, + // if possible. Else, closing will release all cache locks assuming a rollback. + if (session instanceof SessionImplementor) { + ((SessionImplementor) session).afterTransactionCompletion(status == STATUS_COMMITTED, null); + } + // Close the Hibernate Session here if necessary + // (closed in beforeCompletion in case of TransactionManagerLookup). + if (this.newSession) { + SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, this.sessionFactory); + } + else if (!this.hibernateTransactionCompletion) { + session.disconnect(); + } + } + if (!this.newSession && 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(); + } + if (this.sessionHolder.doesNotHoldNonDefaultSession()) { + this.sessionHolder.setSynchronizedWithTransaction(false); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java new file mode 100644 index 00000000000..068d1f9e6b0 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3; + +import java.util.Properties; + +import org.hibernate.ConnectionReleaseMode; +import org.hibernate.Transaction; +import org.hibernate.jdbc.JDBCContext; +import org.hibernate.transaction.JDBCTransaction; +import org.hibernate.transaction.TransactionFactory; + +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Spring-aware implementation of the Hibernate TransactionFactory interface, aware of + * Spring-synchronized transactions (in particular Spring-managed JTA transactions) + * and asking for default release mode ON_CLOSE. Otherwise identical to Hibernate's + * default {@link org.hibernate.transaction.JDBCTransactionFactory} implementation. + * + * @author Juergen Hoeller + * @since 2.5.4 + * @see org.springframework.transaction.support.TransactionSynchronizationManager + * @see org.hibernate.transaction.JDBCTransactionFactory + */ +public class SpringTransactionFactory implements TransactionFactory { + + /** + * Sets connection release mode "on_close" as default. + *
This was the case for Hibernate 3.0; Hibernate 3.1 changed
+ * it to "auto" (i.e. "after_statement" or "after_transaction").
+ * However, for Spring's resource management (in particular for
+ * HibernateTransactionManager), "on_close" is the better default.
+ */
+ public ConnectionReleaseMode getDefaultReleaseMode() {
+ return ConnectionReleaseMode.ON_CLOSE;
+ }
+
+ public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext) {
+ return new JDBCTransaction(jdbcContext, transactionContext);
+ }
+
+ public void configure(Properties props) {
+ }
+
+ public boolean isTransactionManagerRequired() {
+ return false;
+ }
+
+ public boolean areCallbacksLocalToHibernateTransactions() {
+ return true;
+ }
+
+ public boolean isTransactionInProgress(
+ JDBCContext jdbcContext, Context transactionContext, Transaction transaction) {
+
+ return (transaction != null && transaction.isActive()) ||
+ TransactionSynchronizationManager.isActualTransactionActive();
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java
new file mode 100644
index 00000000000..838487a4489
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import javax.sql.DataSource;
+
+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
+
+/**
+ * Subclass of LocalDataSourceConnectionProvider that returns a
+ * transaction-aware proxy for the exposed DataSource. Used if
+ * LocalSessionFactoryBean's "useTransactionAwareDataSource" flag is on.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see LocalSessionFactoryBean#setUseTransactionAwareDataSource
+ */
+public class TransactionAwareDataSourceConnectionProvider extends LocalDataSourceConnectionProvider {
+
+ /**
+ * Return a TransactionAwareDataSourceProxy for the given DataSource,
+ * provided that it isn't a TransactionAwareDataSourceProxy already.
+ */
+ protected DataSource getDataSourceToUse(DataSource originalDataSource) {
+ if (originalDataSource instanceof TransactionAwareDataSourceProxy) {
+ return originalDataSource;
+ }
+ return new TransactionAwareDataSourceProxy(originalDataSource);
+ }
+
+ /**
+ * This implementation returns true: We can guarantee
+ * to receive the same Connection within a transaction, as we are
+ * exposing a TransactionAwareDataSourceProxy.
+ */
+ public boolean supportsAggressiveRelease() {
+ return true;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java
new file mode 100644
index 00000000000..68140afdf89
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3;
+
+import java.util.Properties;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Bean that encapsulates a Hibernate type definition.
+ *
+ *
Typically defined as inner bean within a LocalSessionFactoryBean + * definition, as list element for the "typeDefinitions" bean property. + * For example: + * + *
+ * <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> + * ... + * <property name="typeDefinitions"> + * <list> + * <bean class="org.springframework.orm.hibernate3.TypeDefinitionBean"> + * <property name="typeName" value="myType"/> + * <property name="typeClass" value="mypackage.MyTypeClass"/> + * </bean> + * </list> + * </property> + * ... + * </bean>+ * + * Alternatively, specify a bean id (or name) attribute for the inner bean, + * instead of the "typeName" property. + * + * @author Juergen Hoeller + * @since 1.2 + * @see LocalSessionFactoryBean#setTypeDefinitions(TypeDefinitionBean[]) + */ +public class TypeDefinitionBean implements BeanNameAware, InitializingBean { + + private String typeName; + + private String typeClass; + + private Properties parameters = new Properties(); + + + /** + * Set the name of the type. + * @see org.hibernate.cfg.Mappings#addTypeDef(String, String, java.util.Properties) + */ + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + /** + * Return the name of the type. + */ + public String getTypeName() { + return typeName; + } + + /** + * Set the type implementation class. + * @see org.hibernate.cfg.Mappings#addTypeDef(String, String, java.util.Properties) + */ + public void setTypeClass(String typeClass) { + this.typeClass = typeClass; + } + + /** + * Return the type implementation class. + */ + public String getTypeClass() { + return typeClass; + } + + /** + * Specify default parameters for the type. + * This only applies to parameterized types. + * @see org.hibernate.cfg.Mappings#addTypeDef(String, String, java.util.Properties) + * @see org.hibernate.usertype.ParameterizedType + */ + public void setParameters(Properties parameters) { + this.parameters = parameters; + } + + /** + * Return the default parameters for the type. + */ + public Properties getParameters() { + return parameters; + } + + + /** + * If no explicit type name has been specified, the bean name of + * the TypeDefinitionBean will be used. + * @see #setTypeName + */ + public void setBeanName(String name) { + if (this.typeName == null) { + this.typeName = name; + } + } + + public void afterPropertiesSet() { + if (this.typeName == null) { + throw new IllegalArgumentException("typeName is required"); + } + if (this.typeClass == null) { + throw new IllegalArgumentException("typeClass is required"); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java new file mode 100644 index 00000000000..0560a595cb6 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java @@ -0,0 +1,243 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3.annotation; + +import java.io.IOException; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.MappedSuperclass; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.cfg.AnnotationConfiguration; +import org.hibernate.cfg.Configuration; + +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.orm.hibernate3.LocalSessionFactoryBean; +import org.springframework.util.ClassUtils; + +/** + * Subclass of Spring's standard LocalSessionFactoryBean for Hibernate, + * supporting JDK 1.5+ annotation metadata for mappings. + * + *
Note: This class requires Hibernate 3.2 or higher, with the + * Java Persistence API and the Hibernate Annotations add-on present. + * + *
Example for an AnnotationSessionFactoryBean bean definition: + * + *
+ * <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> + * <property name="dataSource" ref="dataSource"/> + * <property name="annotatedClasses"> + * <list> + * <value>test.package.Foo</value> + * <value>test.package.Bar</value> + * </list> + * </property> + * </bean>+ * + * Or when using classpath scanning for autodetection of entity classes: + * + *
+ * <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> + * <property name="dataSource" ref="dataSource"/> + * <property name="packagesToScan" value="test.package"/> + * </bean>+ * + * @author Juergen Hoeller + * @since 1.2.2 + * @see #setDataSource + * @see #setHibernateProperties + * @see #setAnnotatedClasses + * @see #setAnnotatedPackages + */ +public class AnnotationSessionFactoryBean extends LocalSessionFactoryBean implements ResourceLoaderAware { + + private static final String RESOURCE_PATTERN = "**/*.class"; + + + private Class[] annotatedClasses; + + private String[] annotatedPackages; + + private String[] packagesToScan; + + private TypeFilter[] entityTypeFilters = new TypeFilter[] { + new AnnotationTypeFilter(Entity.class, false), + new AnnotationTypeFilter(Embeddable.class, false), + new AnnotationTypeFilter(MappedSuperclass.class, false)}; + + private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + + public AnnotationSessionFactoryBean() { + setConfigurationClass(AnnotationConfiguration.class); + } + + + public void setConfigurationClass(Class configurationClass) { + if (configurationClass == null || !AnnotationConfiguration.class.isAssignableFrom(configurationClass)) { + throw new IllegalArgumentException( + "AnnotationSessionFactoryBean only supports AnnotationConfiguration or subclasses"); + } + super.setConfigurationClass(configurationClass); + } + + /** + * Specify annotated classes, for which mappings will be read from + * class-level JDK 1.5+ annotation metadata. + * @see org.hibernate.cfg.AnnotationConfiguration#addAnnotatedClass(Class) + */ + public void setAnnotatedClasses(Class[] annotatedClasses) { + this.annotatedClasses = annotatedClasses; + } + + /** + * Specify the names of annotated packages, for which package-level + * JDK 1.5+ annotation metadata will be read. + * @see org.hibernate.cfg.AnnotationConfiguration#addPackage(String) + */ + public void setAnnotatedPackages(String[] annotatedPackages) { + this.annotatedPackages = annotatedPackages; + } + + /** + * Set whether to use Spring-based scanning for entity classes in the classpath + * instead of listing annotated classes explicitly. + *
Default is none. 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 custom type filters for Spring-based scanning for entity classes. + *
Default is to search all specified packages for classes annotated with
+ * @javax.persistence.Entity, @javax.persistence.Embeddable
+ * or @javax.persistence.MappedSuperclass.
+ * @see #setPackagesToScan
+ */
+ public void setEntityTypeFilters(TypeFilter[] entityTypeFilters) {
+ this.entityTypeFilters = entityTypeFilters;
+ }
+
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourcePatternResolver = (resourceLoader != null ?
+ ResourcePatternUtils.getResourcePatternResolver(resourceLoader) :
+ new PathMatchingResourcePatternResolver());
+ }
+
+
+ /**
+ * Reads metadata from annotated classes and packages into the
+ * AnnotationConfiguration instance.
+ */
+ protected void postProcessMappings(Configuration config) throws HibernateException {
+ AnnotationConfiguration annConfig = (AnnotationConfiguration) config;
+ if (this.annotatedClasses != null) {
+ for (int i = 0; i < this.annotatedClasses.length; i++) {
+ annConfig.addAnnotatedClass(this.annotatedClasses[i]);
+ }
+ }
+ if (this.annotatedPackages != null) {
+ for (int i = 0; i < this.annotatedPackages.length; i++) {
+ annConfig.addPackage(this.annotatedPackages[i]);
+ }
+ }
+ scanPackages(annConfig);
+ }
+
+ /**
+ * Perform Spring-based scanning for entity classes.
+ * @see #setPackagesToScan
+ */
+ protected void scanPackages(AnnotationConfiguration 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)) {
+ config.addAnnotatedClass(this.resourcePatternResolver.getClassLoader().loadClass(className));
+ }
+ }
+ }
+ }
+ }
+ 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;
+ }
+
+
+ /**
+ * Delegates to {@link #postProcessAnnotationConfiguration}.
+ */
+ protected final void postProcessConfiguration(Configuration config) throws HibernateException {
+ postProcessAnnotationConfiguration((AnnotationConfiguration) config);
+ }
+
+ /**
+ * To be implemented by subclasses that want to to perform custom
+ * post-processing of the AnnotationConfiguration object after this
+ * FactoryBean performed its default initialization.
+ * @param config the current AnnotationConfiguration object
+ * @throws HibernateException in case of Hibernate initialization errors
+ */
+ protected void postProcessAnnotationConfiguration(AnnotationConfiguration config)
+ throws HibernateException {
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/package.html
new file mode 100644
index 00000000000..744987ba13a
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/annotation/package.html
@@ -0,0 +1,8 @@
+
+
Contains SessionFactory helper classes, a template plus callback +for Hibernate access, and an implementation of Spring's transaction SPI +for local Hibernate transactions. + +
This package supports Hibernate 3.x only. +See the org.springframework.orm.hibernate package for Hibernate 2.1 support. + + + diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java new file mode 100644 index 00000000000..5ab3c68bbbe --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java @@ -0,0 +1,217 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3.support; + +import java.io.IOException; +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.HibernateException; +import org.hibernate.usertype.UserType; +import org.hibernate.util.EqualsHelper; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.jdbc.support.lob.JtaLobCreatorSynchronization; +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.jdbc.support.lob.SpringLobCreatorSynchronization; +import org.springframework.jdbc.support.lob.LobCreatorUtils; +import org.springframework.orm.hibernate3.LocalSessionFactoryBean; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Abstract base class for Hibernate UserType implementations that map to LOBs. + * Retrieves the LobHandler to use from LocalSessionFactoryBean at config time. + * + *
For writing LOBs, either an active Spring transaction synchronization + * or an active JTA transaction (with "jtaTransactionManager" specified on + * LocalSessionFactoryBean or a Hibernate TransactionManagerLookup configured + * through the corresponding Hibernate property) is required. + * + *
Offers template methods for setting parameters and getting result values,
+ * passing in the LobHandler or LobCreator to use.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see org.springframework.jdbc.support.lob.LobHandler
+ * @see org.springframework.jdbc.support.lob.LobCreator
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setJtaTransactionManager
+ */
+public abstract class AbstractLobType implements UserType {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final LobHandler lobHandler;
+
+ private final TransactionManager jtaTransactionManager;
+
+
+ /**
+ * Constructor used by Hibernate: fetches config-time LobHandler and
+ * config-time JTA TransactionManager from LocalSessionFactoryBean.
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeLobHandler
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
+ */
+ protected AbstractLobType() {
+ this(LocalSessionFactoryBean.getConfigTimeLobHandler(),
+ LocalSessionFactoryBean.getConfigTimeTransactionManager());
+ }
+
+ /**
+ * Constructor used for testing: takes an explicit LobHandler
+ * and an explicit JTA TransactionManager (can be null).
+ */
+ protected AbstractLobType(LobHandler lobHandler, TransactionManager jtaTransactionManager) {
+ this.lobHandler = lobHandler;
+ this.jtaTransactionManager = jtaTransactionManager;
+ }
+
+
+ /**
+ * This implementation returns false.
+ */
+ public boolean isMutable() {
+ return false;
+ }
+
+ /**
+ * This implementation delegates to the Hibernate EqualsHelper.
+ * @see org.hibernate.util.EqualsHelper#equals
+ */
+ public boolean equals(Object x, Object y) throws HibernateException {
+ return EqualsHelper.equals(x, y);
+ }
+
+ /**
+ * This implementation returns the hashCode of the given objectz.
+ */
+ public int hashCode(Object x) throws HibernateException {
+ return x.hashCode();
+ }
+
+ /**
+ * This implementation returns the passed-in value as-is.
+ */
+ public Object deepCopy(Object value) throws HibernateException {
+ return value;
+ }
+
+ /**
+ * This implementation returns the passed-in value as-is.
+ */
+ public Serializable disassemble(Object value) throws HibernateException {
+ return (Serializable) value;
+ }
+
+ /**
+ * This implementation returns the passed-in value as-is.
+ */
+ public Object assemble(Serializable cached, Object owner) throws HibernateException {
+ return cached;
+ }
+
+ /**
+ * This implementation returns the passed-in original as-is.
+ */
+ public Object replace(Object original, Object target, Object owner) throws HibernateException {
+ return original;
+ }
+
+
+ /**
+ * This implementation delegates to nullSafeGetInternal,
+ * passing in the LobHandler of this type.
+ * @see #nullSafeGetInternal
+ */
+ public final Object nullSafeGet(ResultSet rs, String[] names, Object owner)
+ throws HibernateException, SQLException {
+
+ if (this.lobHandler == null) {
+ throw new IllegalStateException("No LobHandler found for configuration - " +
+ "lobHandler property must be set on LocalSessionFactoryBean");
+ }
+
+ try {
+ return nullSafeGetInternal(rs, names, owner, this.lobHandler);
+ }
+ catch (IOException ex) {
+ throw new HibernateException("I/O errors during LOB access", ex);
+ }
+ }
+
+ /**
+ * This implementation delegates to nullSafeSetInternal,
+ * passing in a transaction-synchronized LobCreator for the
+ * LobHandler of this type.
+ * @see #nullSafeSetInternal
+ */
+ public final void nullSafeSet(PreparedStatement st, Object value, int index)
+ throws HibernateException, SQLException {
+
+ if (this.lobHandler == null) {
+ throw new IllegalStateException("No LobHandler found for configuration - " +
+ "lobHandler property must be set on LocalSessionFactoryBean");
+ }
+
+ LobCreator lobCreator = this.lobHandler.getLobCreator();
+ try {
+ nullSafeSetInternal(st, index, value, lobCreator);
+ }
+ catch (IOException ex) {
+ throw new HibernateException("I/O errors during LOB access", ex);
+ }
+ LobCreatorUtils.registerTransactionSynchronization(lobCreator, this.jtaTransactionManager);
+ }
+
+ /**
+ * Template method to extract a value from the given result set.
+ * @param rs the ResultSet to extract from
+ * @param names the column names
+ * @param owner the containing entity
+ * @param lobHandler the LobHandler to use
+ * @return the extracted value
+ * @throws SQLException if thrown by JDBC methods
+ * @throws IOException if thrown by streaming methods
+ * @throws HibernateException in case of any other exceptions
+ */
+ protected abstract Object nullSafeGetInternal(
+ ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
+ throws SQLException, IOException, HibernateException;
+
+ /**
+ * Template method to set the given parameter value on the given statement.
+ * @param ps the PreparedStatement to set on
+ * @param index the statement parameter index
+ * @param value the value to set
+ * @param lobCreator the LobCreator to use
+ * @throws SQLException if thrown by JDBC methods
+ * @throws IOException if thrown by streaming methods
+ * @throws HibernateException in case of any other exceptions
+ */
+ protected abstract void nullSafeSetInternal(
+ PreparedStatement ps, int index, Object value, LobCreator lobCreator)
+ throws SQLException, IOException, HibernateException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java
new file mode 100644
index 00000000000..d4f02dd7740
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Arrays;
+
+import javax.transaction.TransactionManager;
+
+import org.springframework.jdbc.support.lob.LobCreator;
+import org.springframework.jdbc.support.lob.LobHandler;
+
+/**
+ * Hibernate UserType implementation for byte arrays that get mapped to BLOBs.
+ * Retrieves the LobHandler to use from LocalSessionFactoryBean at config time.
+ *
+ *
Can also be defined in generic Hibernate mappings, as DefaultLobCreator will
+ * work with most JDBC-compliant database drivers. In this case, the field type
+ * does not have to be BLOB: For databases like MySQL and MS SQL Server, any
+ * large enough binary type will work.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ */
+public class BlobByteArrayType extends AbstractLobType {
+
+ /**
+ * Constructor used by Hibernate: fetches config-time LobHandler and
+ * config-time JTA TransactionManager from LocalSessionFactoryBean.
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeLobHandler
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
+ */
+ public BlobByteArrayType() {
+ super();
+ }
+
+ /**
+ * Constructor used for testing: takes an explicit LobHandler
+ * and an explicit JTA TransactionManager (can be null).
+ */
+ protected BlobByteArrayType(LobHandler lobHandler, TransactionManager jtaTransactionManager) {
+ super(lobHandler, jtaTransactionManager);
+ }
+
+ public int[] sqlTypes() {
+ return new int[] {Types.BLOB};
+ }
+
+ public Class returnedClass() {
+ return byte[].class;
+ }
+
+ public boolean isMutable() {
+ return true;
+ }
+
+ public boolean equals(Object x, Object y) {
+ return (x == y) ||
+ (x instanceof byte[] && y instanceof byte[] && Arrays.equals((byte[]) x, (byte[]) y));
+ }
+
+ public Object deepCopy(Object value) {
+ if (value == null) {
+ return null;
+ }
+ byte[] original = (byte[]) value;
+ byte[] copy = new byte[original.length];
+ System.arraycopy(original, 0, copy, 0, original.length);
+ return copy;
+ }
+
+ protected Object nullSafeGetInternal(
+ ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
+ throws SQLException {
+
+ return lobHandler.getBlobAsBytes(rs, names[0]);
+ }
+
+ protected void nullSafeSetInternal(
+ PreparedStatement ps, int index, Object value, LobCreator lobCreator)
+ throws SQLException {
+
+ lobCreator.setBlobAsBytes(ps, index, (byte[]) value);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java
new file mode 100644
index 00000000000..a8f3865d0af
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import javax.transaction.TransactionManager;
+
+import org.hibernate.HibernateException;
+
+import org.springframework.jdbc.support.lob.LobCreator;
+import org.springframework.jdbc.support.lob.LobHandler;
+
+/**
+ * Hibernate UserType implementation for arbitrary objects that get serialized to BLOBs.
+ * Retrieves the LobHandler to use from LocalSessionFactoryBean at config time.
+ *
+ *
Can also be defined in generic Hibernate mappings, as DefaultLobCreator will + * work with most JDBC-compliant database drivers. In this case, the field type + * does not have to be BLOB: For databases like MySQL and MS SQL Server, any + * large enough binary type will work. + * + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler + */ +public class BlobSerializableType extends AbstractLobType { + + /** + * Initial size for ByteArrayOutputStreams used for serialization output. + *
If a serialized object is larger than these 1024 bytes, the size of
+ * the byte array used by the output stream will be doubled each time the
+ * limit is reached.
+ */
+ private static final int OUTPUT_BYTE_ARRAY_INITIAL_SIZE = 1024;
+
+ /**
+ * Constructor used by Hibernate: fetches config-time LobHandler and
+ * config-time JTA TransactionManager from LocalSessionFactoryBean.
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeLobHandler
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
+ */
+ public BlobSerializableType() {
+ super();
+ }
+
+ /**
+ * Constructor used for testing: takes an explicit LobHandler
+ * and an explicit JTA TransactionManager (can be null).
+ */
+ protected BlobSerializableType(LobHandler lobHandler, TransactionManager jtaTransactionManager) {
+ super(lobHandler, jtaTransactionManager);
+ }
+
+ public int[] sqlTypes() {
+ return new int[] {Types.BLOB};
+ }
+
+ public Class returnedClass() {
+ return Serializable.class;
+ }
+
+ public boolean isMutable() {
+ return true;
+ }
+
+ public Object deepCopy(Object value) throws HibernateException {
+ try {
+ // Write to new byte array to clone.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE);
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ try {
+ oos.writeObject(value);
+ }
+ finally {
+ oos.close();
+ }
+
+ // Read it back and return a true copy.
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ try {
+ return ois.readObject();
+ }
+ finally {
+ ois.close();
+ }
+ }
+ catch (ClassNotFoundException ex) {
+ throw new HibernateException("Couldn't clone BLOB contents", ex);
+ }
+ catch (IOException ex) {
+ throw new HibernateException("Couldn't clone BLOB contents", ex);
+ }
+ }
+
+ protected Object nullSafeGetInternal(
+ ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
+ throws SQLException, IOException, HibernateException {
+
+ InputStream is = lobHandler.getBlobAsBinaryStream(rs, names[0]);
+ if (is != null) {
+ ObjectInputStream ois = new ObjectInputStream(is);
+ try {
+ return ois.readObject();
+ }
+ catch (ClassNotFoundException ex) {
+ throw new HibernateException("Could not deserialize BLOB contents", ex);
+ }
+ finally {
+ ois.close();
+ }
+ }
+ else {
+ return null;
+ }
+ }
+
+ protected void nullSafeSetInternal(
+ PreparedStatement ps, int index, Object value, LobCreator lobCreator)
+ throws SQLException, IOException {
+
+ if (value != null) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE);
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ try {
+ oos.writeObject(value);
+ oos.flush();
+ lobCreator.setBlobAsBytes(ps, index, baos.toByteArray());
+ }
+ finally {
+ oos.close();
+ }
+ }
+ else {
+ lobCreator.setBlobAsBytes(ps, index, null);
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java
new file mode 100644
index 00000000000..a3206bb4d99
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import java.io.UnsupportedEncodingException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import javax.transaction.TransactionManager;
+
+import org.springframework.jdbc.support.lob.LobCreator;
+import org.springframework.jdbc.support.lob.LobHandler;
+
+/**
+ * Hibernate UserType implementation for Strings that get mapped to BLOBs.
+ * Retrieves the LobHandler to use from LocalSessionFactoryBean at config time.
+ *
+ *
This is intended for the (arguably unnatural, but still common) case
+ * where character data is stored in a binary LOB. This requires encoding
+ * and decoding the characters within this UserType; see the javadoc of the
+ * getCharacterEncoding() method.
+ *
+ *
Can also be defined in generic Hibernate mappings, as DefaultLobCreator will
+ * work with most JDBC-compliant database drivers. In this case, the field type
+ * does not have to be BLOB: For databases like MySQL and MS SQL Server, any
+ * large enough binary type will work.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.7
+ * @see #getCharacterEncoding()
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ */
+public class BlobStringType extends AbstractLobType {
+
+ /**
+ * Constructor used by Hibernate: fetches config-time LobHandler and
+ * config-time JTA TransactionManager from LocalSessionFactoryBean.
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeLobHandler
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
+ */
+ public BlobStringType() {
+ super();
+ }
+
+ /**
+ * Constructor used for testing: takes an explicit LobHandler
+ * and an explicit JTA TransactionManager (can be null).
+ */
+ protected BlobStringType(LobHandler lobHandler, TransactionManager jtaTransactionManager) {
+ super(lobHandler, jtaTransactionManager);
+ }
+
+ public int[] sqlTypes() {
+ return new int[] {Types.BLOB};
+ }
+
+ public Class returnedClass() {
+ return String.class;
+ }
+
+ protected Object nullSafeGetInternal(
+ ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
+ throws SQLException, UnsupportedEncodingException {
+
+ byte[] bytes = lobHandler.getBlobAsBytes(rs, names[0]);
+ if (bytes != null) {
+ String encoding = getCharacterEncoding();
+ return (encoding != null ? new String(bytes, encoding) : new String(bytes));
+ }
+ else {
+ return null;
+ }
+ }
+
+ protected void nullSafeSetInternal(
+ PreparedStatement ps, int index, Object value, LobCreator lobCreator)
+ throws SQLException, UnsupportedEncodingException {
+
+ if (value != null) {
+ String str = (String) value;
+ String encoding = getCharacterEncoding();
+ byte[] bytes = (encoding != null ? str.getBytes(encoding) : str.getBytes());
+ lobCreator.setBlobAsBytes(ps, index, bytes);
+ }
+ else {
+ lobCreator.setBlobAsBytes(ps, index, null);
+ }
+ }
+
+ /**
+ * Determine the character encoding to apply to the BLOB's bytes
+ * to turn them into a String.
+ *
Default is null, indicating to use the platform
+ * default encoding. To be overridden in subclasses for a specific
+ * encoding such as "ISO-8859-1" or "UTF-8".
+ * @return the character encoding to use, or null
+ * to use the platform default encoding
+ * @see java.lang.String#String(byte[], String)
+ * @see java.lang.String#getBytes(String)
+ */
+ protected String getCharacterEncoding() {
+ return null;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java
new file mode 100644
index 00000000000..82409b2c7fb
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import javax.transaction.TransactionManager;
+
+import org.springframework.jdbc.support.lob.LobCreator;
+import org.springframework.jdbc.support.lob.LobHandler;
+
+/**
+ * Hibernate UserType implementation for Strings that get mapped to CLOBs.
+ * Retrieves the LobHandler to use from LocalSessionFactoryBean at config time.
+ *
+ *
Particularly useful for storing Strings with more than 4000 characters in an + * Oracle database (only possible via CLOBs), in combination with OracleLobHandler. + * + *
Can also be defined in generic Hibernate mappings, as DefaultLobCreator will
+ * work with most JDBC-compliant database drivers. In this case, the field type
+ * does not have to be CLOB: For databases like MySQL and MS SQL Server, any
+ * large enough character type will work.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ */
+public class ClobStringType extends AbstractLobType {
+
+ /**
+ * Constructor used by Hibernate: fetches config-time LobHandler and
+ * config-time JTA TransactionManager from LocalSessionFactoryBean.
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeLobHandler
+ * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
+ */
+ public ClobStringType() {
+ super();
+ }
+
+ /**
+ * Constructor used for testing: takes an explicit LobHandler
+ * and an explicit JTA TransactionManager (can be null).
+ */
+ protected ClobStringType(LobHandler lobHandler, TransactionManager jtaTransactionManager) {
+ super(lobHandler, jtaTransactionManager);
+ }
+
+ public int[] sqlTypes() {
+ return new int[] {Types.CLOB};
+ }
+
+ public Class returnedClass() {
+ return String.class;
+ }
+
+ protected Object nullSafeGetInternal(
+ ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
+ throws SQLException {
+
+ return lobHandler.getClobAsString(rs, names[0]);
+ }
+
+ protected void nullSafeSetInternal(
+ PreparedStatement ps, int index, Object value, LobCreator lobCreator)
+ throws SQLException {
+
+ lobCreator.setClobAsString(ps, index, (String) value);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java
new file mode 100644
index 00000000000..199f05ae220
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+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.dao.support.DaoSupport;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.SessionFactoryUtils;
+
+/**
+ * Convenient super class for Hibernate-based data access objects.
+ *
+ *
Requires a {@link org.hibernate.SessionFactory} to be set, providing a + * {@link org.springframework.orm.hibernate3.HibernateTemplate} based on it to + * subclasses through the {@link #getHibernateTemplate()} method. + * Can alternatively be initialized directly with a HibernateTemplate, + * in order to reuse the latter's settings such as the SessionFactory, + * exception translator, flush mode, etc. + * + *
This base class is mainly intended for HibernateTemplate usage but can + * also be used when working with a Hibernate Session directly, for example + * when relying on transactional Sessions. Convenience {@link #getSession} + * and {@link #releaseSession} methods are provided for that usage style. + * + *
This class will create its own HibernateTemplate instance if a SessionFactory + * is passed in. The "allowCreate" flag on that HibernateTemplate will be "true" + * by default. A custom HibernateTemplate instance can be used through overriding + * {@link #createHibernateTemplate}. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setSessionFactory + * @see #getHibernateTemplate + * @see org.springframework.orm.hibernate3.HibernateTemplate + */ +public abstract class HibernateDaoSupport extends DaoSupport { + + private HibernateTemplate hibernateTemplate; + + + /** + * Set the Hibernate SessionFactory to be used by this DAO. + * Will automatically create a HibernateTemplate for the given SessionFactory. + * @see #createHibernateTemplate + * @see #setHibernateTemplate + */ + public final void setSessionFactory(SessionFactory sessionFactory) { + if (this.hibernateTemplate == null || sessionFactory != this.hibernateTemplate.getSessionFactory()) { + this.hibernateTemplate = createHibernateTemplate(sessionFactory); + } + } + + /** + * Create a HibernateTemplate for the given SessionFactory. + * Only invoked if populating the DAO with a SessionFactory reference! + *
Can be overridden in subclasses to provide a HibernateTemplate instance + * with different configuration, or a custom HibernateTemplate subclass. + * @param sessionFactory the Hibernate SessionFactory to create a HibernateTemplate for + * @return the new HibernateTemplate instance + * @see #setSessionFactory + */ + protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) { + return new HibernateTemplate(sessionFactory); + } + + /** + * Return the Hibernate SessionFactory used by this DAO. + */ + public final SessionFactory getSessionFactory() { + return (this.hibernateTemplate != null ? this.hibernateTemplate.getSessionFactory() : null); + } + + /** + * Set the HibernateTemplate for this DAO explicitly, + * as an alternative to specifying a SessionFactory. + * @see #setSessionFactory + */ + public final void setHibernateTemplate(HibernateTemplate hibernateTemplate) { + this.hibernateTemplate = hibernateTemplate; + } + + /** + * Return the HibernateTemplate for this DAO, + * pre-initialized with the SessionFactory or set explicitly. + *
Note: The returned HibernateTemplate is a shared instance.
+ * You may introspect its configuration, but not modify the configuration
+ * (other than from within an {@link #initDao} implementation).
+ * Consider creating a custom HibernateTemplate instance via
+ * new HibernateTemplate(getSessionFactory()), in which
+ * case you're allowed to customize the settings on the resulting instance.
+ */
+ public final HibernateTemplate getHibernateTemplate() {
+ return this.hibernateTemplate;
+ }
+
+ protected final void checkDaoConfig() {
+ if (this.hibernateTemplate == null) {
+ throw new IllegalArgumentException("'sessionFactory' or 'hibernateTemplate' is required");
+ }
+ }
+
+
+ /**
+ * Obtain a Hibernate Session, either from the current transaction or
+ * a new one. The latter is only allowed if the
+ * {@link org.springframework.orm.hibernate3.HibernateTemplate#setAllowCreate "allowCreate"}
+ * setting of this bean's {@link #setHibernateTemplate HibernateTemplate} is "true".
+ *
Note that this is not meant to be invoked from HibernateTemplate code + * but rather just in plain Hibernate code. Either rely on a thread-bound + * Session or use it in combination with {@link #releaseSession}. + *
In general, it is recommended to use HibernateTemplate, either with + * the provided convenience operations or with a custom HibernateCallback + * that provides you with a Session to work on. HibernateTemplate will care + * for all resource management and for proper exception conversion. + * @return the Hibernate Session + * @throws DataAccessResourceFailureException if the Session couldn't be created + * @throws IllegalStateException if no thread-bound Session found and allowCreate=false + * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean) + */ + protected final Session getSession() + throws DataAccessResourceFailureException, IllegalStateException { + + return getSession(this.hibernateTemplate.isAllowCreate()); + } + + /** + * Obtain a Hibernate Session, either from the current transaction or + * a new one. The latter is only allowed if "allowCreate" is true. + *
Note that this is not meant to be invoked from HibernateTemplate code + * but rather just in plain Hibernate code. Either rely on a thread-bound + * Session or use it in combination with {@link #releaseSession}. + *
In general, it is recommended to use
+ * {@link #getHibernateTemplate() HibernateTemplate}, either with
+ * the provided convenience operations or with a custom
+ * {@link org.springframework.orm.hibernate3.HibernateCallback} that
+ * provides you with a Session to work on. HibernateTemplate will care
+ * for all resource management and for proper exception conversion.
+ * @param allowCreate if a non-transactional Session should be created when no
+ * transactional Session can be found for the current thread
+ * @return the Hibernate Session
+ * @throws DataAccessResourceFailureException if the Session couldn't be created
+ * @throws IllegalStateException if no thread-bound Session found and allowCreate=false
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
+ */
+ protected final Session getSession(boolean allowCreate)
+ throws DataAccessResourceFailureException, IllegalStateException {
+
+ return (!allowCreate ?
+ SessionFactoryUtils.getSession(getSessionFactory(), false) :
+ SessionFactoryUtils.getSession(
+ getSessionFactory(),
+ this.hibernateTemplate.getEntityInterceptor(),
+ this.hibernateTemplate.getJdbcExceptionTranslator()));
+ }
+
+ /**
+ * Convert the given HibernateException to an appropriate exception from the
+ * org.springframework.dao hierarchy. Will automatically detect
+ * wrapped SQLExceptions and convert them accordingly.
+ *
Delegates to the + * {@link org.springframework.orm.hibernate3.HibernateTemplate#convertHibernateAccessException} + * method of this DAO's HibernateTemplate. + *
Typically used in plain Hibernate code, in combination with + * {@link #getSession} and {@link #releaseSession}. + * @param ex HibernateException that occured + * @return the corresponding DataAccessException instance + * @see org.springframework.orm.hibernate3.SessionFactoryUtils#convertHibernateAccessException + */ + protected final DataAccessException convertHibernateAccessException(HibernateException ex) { + return this.hibernateTemplate.convertHibernateAccessException(ex); + } + + /** + * Close the given Hibernate Session, created via this DAO's SessionFactory, + * if it isn't bound to the thread (i.e. isn't a transactional Session). + *
Typically used in plain Hibernate code, in combination with
+ * {@link #getSession} and {@link #convertHibernateAccessException}.
+ * @param session the Session to close
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#releaseSession
+ */
+ protected final void releaseSession(Session session) {
+ SessionFactoryUtils.releaseSession(session, getSessionFactory());
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java
new file mode 100644
index 00000000000..d067a3fce53
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.event.MergeEvent;
+import org.hibernate.event.def.DefaultMergeEventListener;
+import org.hibernate.persister.entity.EntityPersister;
+
+/**
+ * Extension of Hibernate's DefaultMergeEventListener, transferring the ids
+ * of newly saved objects to the corresponding original objects (that are part
+ * of the detached object graph passed into the merge method).
+ *
+ *
Transferring newly assigned ids to the original graph allows for continuing + * to use the original object graph, despite merged copies being registered with + * the current Hibernate Session. This is particularly useful for web applications + * that might want to store an object graph and then render it in a web view, + * with links that include the id of certain (potentially newly saved) objects. + * + *
The merge behavior given by this MergeEventListener is nearly identical
+ * to TopLink's merge behavior. See PetClinic for an example, which relies on
+ * ids being available for newly saved objects: the HibernateClinic
+ * and TopLinkClinic DAO implementations both use straight merge
+ * calls, with the Hibernate SessionFactory configuration specifying an
+ * IdTransferringMergeEventListener.
+ *
+ *
Typically specified as entry for LocalSessionFactoryBean's "eventListeners" + * map, with key "merge". + * + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setEventListeners(java.util.Map) + */ +public class IdTransferringMergeEventListener extends DefaultMergeEventListener { + + /** + * Hibernate 3.1 implementation of ID transferral. + */ + protected void entityIsTransient(MergeEvent event, Map copyCache) { + super.entityIsTransient(event, copyCache); + SessionImplementor session = event.getSession(); + EntityPersister persister = session.getEntityPersister(event.getEntityName(), event.getEntity()); + // Extract id from merged copy (which is currently registered with Session). + Serializable id = persister.getIdentifier(event.getResult(), session.getEntityMode()); + // Set id on original object (which remains detached). + persister.setIdentifier(event.getOriginal(), id, session.getEntityMode()); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java new file mode 100644 index 00000000000..b3eaec38e47 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java @@ -0,0 +1,283 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3.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.Session; +import org.hibernate.SessionFactory; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.orm.hibernate3.SessionFactoryUtils; +import org.springframework.orm.hibernate3.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire + * processing of the request. Intended for the "Open Session in View" pattern, + * i.e. to allow for lazy loading in web views despite the original transactions + * already being completed. + * + *
This filter makes Hibernate Sessions available via the current thread, which + * will be autodetected by transaction managers. It is suitable for service layer + * transactions via {@link org.springframework.orm.hibernate3.HibernateTransactionManager} + * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well + * as for non-transactional execution (if configured appropriately). + * + *
NOTE: This filter will by default not flush the Hibernate Session,
+ * with the flush mode set to FlushMode.NEVER. It assumes to be used
+ * in combination with service layer transactions that care for the flushing: The
+ * active transaction manager will temporarily change the flush mode to
+ * FlushMode.AUTO during a read-write transaction, with the flush
+ * mode reset to FlushMode.NEVER at the end of each transaction.
+ * If you intend to use this filter without transactions, consider changing
+ * the default flush mode (through the "flushMode" property).
+ *
+ *
WARNING: Applying this filter to existing logic can cause issues that + * have not appeared before, through the use of a single Hibernate Session for the + * processing of an entire request. In particular, the reassociation of persistent + * objects with a Hibernate Session has to occur at the very beginning of request + * processing, to avoid clashes with already loaded instances of the same objects. + * + *
Alternatively, turn this filter into deferred close mode, by specifying + * "singleSession"="false": It will not use a single session per request then, + * but rather let each data access operation or transaction use its own session + * (like without Open Session in View). Each of those sessions will be registered + * for deferred close, though, actually processed at request completion. + * + *
A single session per request allows for most efficient first-level caching,
+ * but can cause side effects, for example on saveOrUpdate or when
+ * continuing after a rolled-back transaction. The deferred close strategy is as safe
+ * as no Open Session in View in that respect, while still allowing for lazy loading
+ * in views (but not providing a first-level cache for the entire request).
+ *
+ *
Looks up the SessionFactory in Spring's root web application context.
+ * Supports a "sessionFactoryBeanName" filter init-param in web.xml;
+ * the default bean name is "sessionFactory". Looks up the SessionFactory on each
+ * request, to avoid initialization order issues (when using ContextLoaderServlet,
+ * the root application context will get initialized after this filter).
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #setSingleSession
+ * @see #setFlushMode
+ * @see #lookupSessionFactory
+ * @see OpenSessionInViewInterceptor
+ * @see org.springframework.orm.hibernate3.HibernateInterceptor
+ * @see org.springframework.orm.hibernate3.HibernateTransactionManager
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
+ * @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;
+
+ private boolean singleSession = true;
+
+ private FlushMode flushMode = FlushMode.NEVER;
+
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Set whether to use a single session for each request. Default is "true".
+ *
If set to "false", each data access operation or transaction will use + * its own session (like without Open Session in View). Each of those + * sessions will be registered for deferred close, though, actually + * processed at request completion. + * @see SessionFactoryUtils#initDeferredClose + * @see SessionFactoryUtils#processDeferredClose + */ + public void setSingleSession(boolean singleSession) { + this.singleSession = singleSession; + } + + /** + * Return whether to use a single session for each request. + */ + protected boolean isSingleSession() { + return this.singleSession; + } + + /** + * Specify the Hibernate FlushMode to apply to this filter's + * {@link org.hibernate.Session}. Only applied in single session mode. + *
Can be populated with the corresponding constant name in XML bean + * definitions: e.g. "AUTO". + *
The default is "NEVER". Specify "AUTO" if you intend to use + * this filter without service layer transactions. + * @see org.hibernate.Session#setFlushMode + * @see org.hibernate.FlushMode#NEVER + * @see org.hibernate.FlushMode#AUTO + */ + public void setFlushMode(FlushMode flushMode) { + this.flushMode = flushMode; + } + + /** + * Return the Hibernate FlushMode that this filter applies to its + * {@link org.hibernate.Session} (in single session mode). + */ + protected FlushMode getFlushMode() { + return this.flushMode; + } + + + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + SessionFactory sessionFactory = lookupSessionFactory(request); + boolean participate = false; + + if (isSingleSession()) { + // single session mode + if (TransactionSynchronizationManager.hasResource(sessionFactory)) { + // Do not modify the Session: just set the participate flag. + participate = true; + } + else { + logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter"); + Session session = getSession(sessionFactory); + TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); + } + } + else { + // deferred close mode + if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) { + // Do not modify deferred close: just set the participate flag. + participate = true; + } + else { + SessionFactoryUtils.initDeferredClose(sessionFactory); + } + } + + try { + filterChain.doFilter(request, response); + } + + finally { + if (!participate) { + if (isSingleSession()) { + // single session mode + SessionHolder sessionHolder = + (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory); + logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter"); + closeSession(sessionHolder.getSession(), sessionFactory); + } + else { + // deferred close mode + SessionFactoryUtils.processDeferredClose(sessionFactory); + } + } + } + } + + /** + * Look up the SessionFactory that this filter should use, + * taking the current HTTP request as argument. + *
The default implementation delegates to the {@link #lookupSessionFactory()} + * variant without arguments. + * @param request the current request + * @return the SessionFactory to use + */ + protected SessionFactory lookupSessionFactory(HttpServletRequest request) { + return lookupSessionFactory(); + } + + /** + * Look up the SessionFactory that this filter should use. + *
The default implementation looks for a bean with the specified name + * in Spring's root application context. + * @return the SessionFactory to use + * @see #getSessionFactoryBeanName + */ + protected SessionFactory lookupSessionFactory() { + if (logger.isDebugEnabled()) { + logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter"); + } + WebApplicationContext wac = + WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + return (SessionFactory) wac.getBean(getSessionFactoryBeanName(), SessionFactory.class); + } + + /** + * Get a Session for the SessionFactory that this filter uses. + * Note that this just applies in single session mode! + *
The default implementation delegates to the
+ * SessionFactoryUtils.getSession method and
+ * sets the Session's flush mode to "NEVER".
+ *
Can be overridden in subclasses for creating a Session with a + * custom entity interceptor or JDBC exception translator. + * @param sessionFactory the SessionFactory that this filter uses + * @return the Session to use + * @throws DataAccessResourceFailureException if the Session could not be created + * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean) + * @see org.hibernate.FlushMode#NEVER + */ + protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException { + Session session = SessionFactoryUtils.getSession(sessionFactory, true); + FlushMode flushMode = getFlushMode(); + if (flushMode != null) { + session.setFlushMode(flushMode); + } + return session; + } + + /** + * Close the given Session. + * Note that this just applies in single session mode! + *
Can be overridden in subclasses, e.g. for flushing the Session before
+ * closing it. See class-level javadoc for a discussion of flush handling.
+ * Note that you should also override getSession accordingly, to set
+ * the flush mode to something else than NEVER.
+ * @param session the Session used for filtering
+ * @param sessionFactory the SessionFactory that this filter uses
+ */
+ protected void closeSession(Session session, SessionFactory sessionFactory) {
+ SessionFactoryUtils.closeSession(session);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java
new file mode 100644
index 00000000000..4a31dae7d52
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.orm.hibernate3.HibernateAccessor;
+import org.springframework.orm.hibernate3.SessionFactoryUtils;
+import org.springframework.orm.hibernate3.SessionHolder;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.context.request.WebRequestInterceptor;
+
+/**
+ * Spring web request interceptor that binds a Hibernate Session to the
+ * thread for the entire processing of the request.
+ *
+ *
This class is a concrete expression of the "Open Session in View" pattern, which + * is a pattern that allows for the lazy loading of associations in web views despite + * the original transactions already being completed. + * + *
This interceptor makes Hibernate Sessions available via the current
+ * thread, which will be autodetected by transaction managers. It is suitable for
+ * service layer transactions via
+ * {@link org.springframework.orm.hibernate3.HibernateTransactionManager} or
+ * {@link org.springframework.transaction.jta.JtaTransactionManager} as well as for
+ * non-transactional execution (if configured appropriately).
+ *
+ *
NOTE: This interceptor will by default not flush the Hibernate
+ * Session, with the flush mode being set to FlushMode.NEVER.
+ * It assumes that it will be used in combination with service layer transactions
+ * that handle the flushing: the active transaction manager will temporarily change
+ * the flush mode to FlushMode.AUTO during a read-write transaction,
+ * with the flush mode reset to FlushMode.NEVER at the end of each
+ * transaction. If you intend to use this interceptor without transactions, consider
+ * changing the default flush mode (through the
+ * {@link #setFlushMode(int) "flushMode"} property).
+ *
+ *
In contrast to {@link OpenSessionInViewFilter}, this interceptor is + * configured in a Spring application context and can thus take advantage of bean + * wiring. It inherits common Hibernate configuration properties from + * {@link org.springframework.orm.hibernate3.HibernateAccessor}, + * to be configured in a bean definition. + * + *
WARNING: Applying this interceptor to existing logic can cause issues
+ * that have not appeared before, through the use of a single Hibernate
+ * Session for the processing of an entire request. In particular, the
+ * reassociation of persistent objects with a Hibernate Session has to
+ * occur at the very beginning of request processing, to avoid clashes with already
+ * loaded instances of the same objects.
+ *
+ *
Alternatively, turn this interceptor into deferred close mode, by specifying + * "singleSession"="false": It will not use a single session per request then, + * but rather let each data access operation or transaction use its own session + * (as would be the case without Open Session in View). Each of those sessions will + * be registered for deferred close though, which will actually be processed at + * request completion. + * + *
A single session per request allows for the most efficient first-level caching,
+ * but can cause side effects, for example on saveOrUpdate or when
+ * continuing after a rolled-back transaction. The deferred close strategy is as safe
+ * as no Open Session in View in that respect, while still allowing for lazy loading
+ * in views (but not providing a first-level cache for the entire request).
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #setSingleSession
+ * @see #setFlushMode
+ * @see OpenSessionInViewFilter
+ * @see org.springframework.orm.hibernate3.HibernateInterceptor
+ * @see org.springframework.orm.hibernate3.HibernateTransactionManager
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public class OpenSessionInViewInterceptor extends HibernateAccessor implements WebRequestInterceptor {
+
+ /**
+ * Suffix that gets appended to the SessionFactory
+ * toString() representation for the "participate in existing
+ * session handling" request attribute.
+ * @see #getParticipateAttributeName
+ */
+ public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
+
+
+ private boolean singleSession = true;
+
+
+ /**
+ * Create a new OpenSessionInViewInterceptor,
+ * turning the default flushMode to FLUSH_NEVER.
+ * @see #setFlushMode
+ */
+ public OpenSessionInViewInterceptor() {
+ setFlushMode(FLUSH_NEVER);
+ }
+
+ /**
+ * Set whether to use a single session for each request. Default is "true".
+ *
If set to false, each data access operation or transaction will use
+ * its own session (like without Open Session in View). Each of those
+ * sessions will be registered for deferred close, though, actually
+ * processed at request completion.
+ * @see SessionFactoryUtils#initDeferredClose
+ * @see SessionFactoryUtils#processDeferredClose
+ */
+ public void setSingleSession(boolean singleSession) {
+ this.singleSession = singleSession;
+ }
+
+ /**
+ * Return whether to use a single session for each request.
+ */
+ protected boolean isSingleSession() {
+ return singleSession;
+ }
+
+
+ /**
+ * Open a new Hibernate Session according to the settings of this
+ * HibernateAccessor and bind it to the thread via the
+ * {@link TransactionSynchronizationManager}.
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
+ */
+ public void preHandle(WebRequest request) throws DataAccessException {
+ if ((isSingleSession() && TransactionSynchronizationManager.hasResource(getSessionFactory())) ||
+ SessionFactoryUtils.isDeferredCloseActive(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.intValue() + 1 : 1;
+ request.setAttribute(getParticipateAttributeName(), new Integer(newCount), WebRequest.SCOPE_REQUEST);
+ }
+ else {
+ if (isSingleSession()) {
+ // single session mode
+ logger.debug("Opening single Hibernate Session in OpenSessionInViewInterceptor");
+ Session session = SessionFactoryUtils.getSession(
+ getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
+ applyFlushMode(session, false);
+ TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session));
+ }
+ else {
+ // deferred close mode
+ SessionFactoryUtils.initDeferredClose(getSessionFactory());
+ }
+ }
+ }
+
+ /**
+ * Flush the Hibernate Session before view rendering, if necessary.
+ *
Note that this just applies in {@link #isSingleSession() single session mode}! + *
The default is FLUSH_NEVER to avoid this extra flushing,
+ * assuming that service layer transactions have flushed their changes on commit.
+ * @see #setFlushMode
+ */
+ public void postHandle(WebRequest request, ModelMap model) throws DataAccessException {
+ if (isSingleSession()) {
+ // Only potentially flush in single session mode.
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
+ logger.debug("Flushing single Hibernate Session in OpenSessionInViewInterceptor");
+ try {
+ flushIfNecessary(sessionHolder.getSession(), false);
+ }
+ catch (HibernateException ex) {
+ throw convertHibernateAccessException(ex);
+ }
+ }
+ }
+
+ /**
+ * Unbind the Hibernate Session from the thread and close it (in
+ * single session mode), or process deferred close for all sessions that have
+ * been opened during the current request (in deferred close mode).
+ * @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.intValue() > 1) {
+ request.setAttribute(participateAttributeName, new Integer(count.intValue() - 1), WebRequest.SCOPE_REQUEST);
+ }
+ else {
+ request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
+ }
+ }
+ else {
+ if (isSingleSession()) {
+ // single session mode
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
+ logger.debug("Closing single Hibernate Session in OpenSessionInViewInterceptor");
+ SessionFactoryUtils.closeSession(sessionHolder.getSession());
+ }
+ else {
+ // deferred close mode
+ SessionFactoryUtils.processDeferredClose(getSessionFactory());
+ }
+ }
+ }
+
+ /**
+ * Return the name of the request attribute that identifies that a request is
+ * already intercepted.
+ *
The default implementation takes the toString() representation
+ * of the SessionFactory instance and appends {@link #PARTICIPATE_SUFFIX}.
+ */
+ protected String getParticipateAttributeName() {
+ return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java
new file mode 100644
index 00000000000..9ea0b94946c
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.hibernate3.support;
+
+import org.hibernate.EmptyInterceptor;
+
+import org.springframework.aop.scope.ScopedObject;
+import org.springframework.aop.support.AopUtils;
+
+/**
+ * Hibernate3 interceptor used for getting the proper entity name for scoped
+ * beans. As scoped bean classes are proxies generated at runtime, they are
+ * unrecognized by the persisting framework. Using this interceptor, the
+ * original scoped bean class is retrieved end exposed to Hibernate for
+ * persisting.
+ *
+ *
Usage example: + * + *
+ * <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> + * ... + * <property name="entityInterceptor"> + * <bean class="org.springframework.orm.hibernate3.support.ScopedBeanInterceptor"/> + * </property> + * </bean>+ * + * @author Costin Leau + * @author Juergen Hoeller + * @since 2.0 + */ +public class ScopedBeanInterceptor extends EmptyInterceptor { + + public String getEntityName(Object entity) { + if (entity instanceof ScopedObject) { + // Determine underlying object's type. + Object targetObject = ((ScopedObject) entity).getTargetObject(); + return AopUtils.getTargetClass(targetObject).getName(); + } + + // Any other object: delegate to the default implementation. + return super.getEntityName(entity); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/package.html new file mode 100644 index 00000000000..1ef04906ded --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/support/package.html @@ -0,0 +1,8 @@ + + + +Classes supporting the
org.springframework.orm.hibernate3 package.
+Contains a DAO base class for HibernateTemplate usage.
+
+
+
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientCallback.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientCallback.java
new file mode 100644
index 00000000000..0723464d1e0
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientCallback.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2006 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.ibatis;
+
+import java.sql.SQLException;
+
+import com.ibatis.sqlmap.client.SqlMapExecutor;
+
+/**
+ * Callback interface for data access code that works with the iBATIS
+ * {@link com.ibatis.sqlmap.client.SqlMapExecutor} interface. To be used
+ * with {@link SqlMapClientTemplate}'s execute method,
+ * assumably often as anonymous classes within a method implementation.
+ *
+ * @author Juergen Hoeller
+ * @since 24.02.2004
+ * @see SqlMapClientTemplate
+ * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
+ */
+public interface SqlMapClientCallback {
+
+ /**
+ * Gets called by SqlMapClientTemplate.execute with an active
+ * SqlMapExecutor. Does not need to care about activating
+ * or closing the SqlMapExecutor, or handling transactions.
+ *
+ * If called without a thread-bound JDBC transaction (initiated by + * DataSourceTransactionManager), the code will simply get executed on the + * underlying JDBC connection with its transactional semantics. If using + * a JTA-aware DataSource, the JDBC connection and thus the callback code + * will be transactional if a JTA transaction is active. + * + *
Allows for returning a result object created within the callback,
+ * i.e. a domain object or a collection of domain objects.
+ * A thrown custom RuntimeException is treated as an application exception:
+ * It gets propagated to the caller of the template.
+ *
+ * @param executor an active iBATIS SqlMapSession, passed-in as
+ * SqlMapExecutor interface here to avoid manual lifecycle handling
+ * @return a result object, or null if none
+ * @throws SQLException if thrown by the iBATIS SQL Maps API
+ * @see SqlMapClientTemplate#execute
+ * @see SqlMapClientTemplate#executeWithListResult
+ * @see SqlMapClientTemplate#executeWithMapResult
+ */
+ Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientFactoryBean.java
new file mode 100644
index 00000000000..603e908b345
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientFactoryBean.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.ibatis;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import com.ibatis.common.xml.NodeletException;
+import com.ibatis.sqlmap.client.SqlMapClient;
+import com.ibatis.sqlmap.client.SqlMapClientBuilder;
+import com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser;
+import com.ibatis.sqlmap.engine.builder.xml.SqlMapParser;
+import com.ibatis.sqlmap.engine.builder.xml.XmlParserState;
+import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;
+import com.ibatis.sqlmap.engine.transaction.TransactionConfig;
+import com.ibatis.sqlmap.engine.transaction.TransactionManager;
+import com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.NestedIOException;
+import org.springframework.core.io.Resource;
+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
+import org.springframework.jdbc.support.lob.LobHandler;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * {@link org.springframework.beans.factory.FactoryBean} that creates an
+ * iBATIS {@link com.ibatis.sqlmap.client.SqlMapClient}. This is the usual
+ * way to set up a shared iBATIS SqlMapClient in a Spring application context;
+ * the SqlMapClient can then be passed to iBATIS-based DAOs via dependency
+ * injection.
+ *
+ *
Either {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * or {@link org.springframework.transaction.jta.JtaTransactionManager} can be + * used for transaction demarcation in combination with a SqlMapClient, + * with JTA only necessary for transactions which span multiple databases. + * + *
Allows for specifying a DataSource at the SqlMapClient level. This + * is preferable to per-DAO DataSource references, as it allows for lazy + * loading and avoids repeated DataSource references in every DAO. + * + *
Note: As of Spring 2.5.5, this class (finally) requires iBATIS 2.3 + * or higher. The new "mappingLocations" feature requires iBATIS 2.3.2. + * + * @author Juergen Hoeller + * @since 24.02.2004 + * @see #setConfigLocation + * @see #setDataSource + * @see SqlMapClientTemplate#setSqlMapClient + * @see SqlMapClientTemplate#setDataSource + */ +public class SqlMapClientFactoryBean implements FactoryBean, InitializingBean { + + private static final ThreadLocal configTimeLobHandlerHolder = new ThreadLocal(); + + /** + * Return the LobHandler for the currently configured iBATIS SqlMapClient, + * to be used by TypeHandler implementations like ClobStringTypeHandler. + *
This instance will be set before initialization of the corresponding + * SqlMapClient, and reset immediately afterwards. It is thus only available + * during configuration. + * @see #setLobHandler + * @see org.springframework.orm.ibatis.support.ClobStringTypeHandler + * @see org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler + * @see org.springframework.orm.ibatis.support.BlobSerializableTypeHandler + */ + public static LobHandler getConfigTimeLobHandler() { + return (LobHandler) configTimeLobHandlerHolder.get(); + } + + + private Resource[] configLocations; + + private Resource[] mappingLocations; + + private Properties sqlMapClientProperties; + + private DataSource dataSource; + + private boolean useTransactionAwareDataSource = true; + + private Class transactionConfigClass = ExternalTransactionConfig.class; + + private Properties transactionConfigProperties; + + private LobHandler lobHandler; + + private SqlMapClient sqlMapClient; + + + public SqlMapClientFactoryBean() { + this.transactionConfigProperties = new Properties(); + this.transactionConfigProperties.setProperty("SetAutoCommitAllowed", "false"); + } + + /** + * Set the location of the iBATIS SqlMapClient config file. + * A typical value is "WEB-INF/sql-map-config.xml". + * @see #setConfigLocations + */ + public void setConfigLocation(Resource configLocation) { + this.configLocations = (configLocation != null ? new Resource[] {configLocation} : null); + } + + /** + * Set multiple locations of iBATIS SqlMapClient config files that + * are going to be merged into one unified configuration at runtime. + */ + public void setConfigLocations(Resource[] configLocations) { + this.configLocations = configLocations; + } + + /** + * Set locations of iBATIS sql-map mapping files that are going to be + * merged into the SqlMapClient configuration at runtime. + *
This is an alternative to specifying "<sqlMap>" entries + * in a sql-map-client config file. This property being based on Spring's + * resource abstraction also allows for specifying resource patterns here: + * e.g. "/myApp/*-map.xml". + *
Note that this feature requires iBATIS 2.3.2; it will not work
+ * with any previous iBATIS version.
+ */
+ public void setMappingLocations(Resource[] mappingLocations) {
+ this.mappingLocations = mappingLocations;
+ }
+
+ /**
+ * Set optional properties to be passed into the SqlMapClientBuilder, as
+ * alternative to a <properties> tag in the sql-map-config.xml
+ * file. Will be used to resolve placeholders in the config file.
+ * @see #setConfigLocation
+ * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient(java.io.InputStream, java.util.Properties)
+ */
+ public void setSqlMapClientProperties(Properties sqlMapClientProperties) {
+ this.sqlMapClientProperties = sqlMapClientProperties;
+ }
+
+ /**
+ * Set the DataSource to be used by iBATIS SQL Maps. This will be passed to the
+ * SqlMapClient as part of a TransactionConfig instance.
+ *
If specified, this will override corresponding settings in the SqlMapClient + * properties. Usually, you will specify DataSource and transaction configuration + * either here or in SqlMapClient properties. + *
Specifying a DataSource for the SqlMapClient rather than for each individual + * DAO allows for lazy loading, for example when using PaginatedList results. + *
With a DataSource passed in here, you don't need to specify one for each DAO. + * Passing the SqlMapClient to the DAOs is enough, as it already carries a DataSource. + * Thus, it's recommended to specify the DataSource at this central location only. + *
Thanks to Brandon Goodin from the iBATIS team for the hint on how to make + * this work with Spring's integration strategy! + * @see #setTransactionConfigClass + * @see #setTransactionConfigProperties + * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource + * @see SqlMapClientTemplate#setDataSource + * @see SqlMapClientTemplate#queryForPaginatedList + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * Set whether to use a transaction-aware DataSource for the SqlMapClient, + * i.e. whether to automatically wrap the passed-in DataSource with Spring's + * TransactionAwareDataSourceProxy. + *
Default is "true": When the SqlMapClient performs direct database operations + * outside of Spring's SqlMapClientTemplate (for example, lazy loading or direct + * SqlMapClient access), it will still participate in active Spring-managed + * transactions. + *
As a further effect, using a transaction-aware DataSource will apply + * remaining transaction timeouts to all created JDBC Statements. This means + * that all operations performed by the SqlMapClient will automatically + * participate in Spring-managed transaction timeouts. + *
Turn this flag off to get raw DataSource handling, without Spring transaction
+ * checks. Operations on Spring's SqlMapClientTemplate will still detect
+ * Spring-managed transactions, but lazy loading or direct SqlMapClient access won't.
+ * @see #setDataSource
+ * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
+ * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
+ * @see SqlMapClientTemplate
+ * @see com.ibatis.sqlmap.client.SqlMapClient
+ */
+ public void setUseTransactionAwareDataSource(boolean useTransactionAwareDataSource) {
+ this.useTransactionAwareDataSource = useTransactionAwareDataSource;
+ }
+
+ /**
+ * Set the iBATIS TransactionConfig class to use. Default is
+ * com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig.
+ *
Will only get applied when using a Spring-managed DataSource. + * An instance of this class will get populated with the given DataSource + * and initialized with the given properties. + *
The default ExternalTransactionConfig is appropriate if there is + * external transaction management that the SqlMapClient should participate + * in: be it Spring transaction management, EJB CMT or plain JTA. This + * should be the typical scenario. If there is no active transaction, + * SqlMapClient operations will execute SQL statements non-transactionally. + *
JdbcTransactionConfig or JtaTransactionConfig is only necessary + * when using the iBATIS SqlMapTransactionManager API instead of external + * transactions. If there is no explicit transaction, SqlMapClient operations + * will automatically start a transaction for their own scope (in contrast + * to the external transaction mode, see above). + *
It is strongly recommended to use iBATIS SQL Maps with Spring + * transaction management (or EJB CMT). In this case, the default + * ExternalTransactionConfig is fine. Lazy loading and SQL Maps operations + * without explicit transaction demarcation will execute non-transactionally. + *
Even with Spring transaction management, it might be desirable to + * specify JdbcTransactionConfig: This will still participate in existing + * Spring-managed transactions, but lazy loading and operations without + * explicit transaction demaration will execute in their own auto-started + * transactions. However, this is usually not necessary. + * @see #setDataSource + * @see #setTransactionConfigProperties + * @see com.ibatis.sqlmap.engine.transaction.TransactionConfig + * @see com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig + * @see com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig + * @see com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig + * @see com.ibatis.sqlmap.client.SqlMapTransactionManager + */ + public void setTransactionConfigClass(Class transactionConfigClass) { + if (transactionConfigClass == null || !TransactionConfig.class.isAssignableFrom(transactionConfigClass)) { + throw new IllegalArgumentException("Invalid transactionConfigClass: does not implement " + + "com.ibatis.sqlmap.engine.transaction.TransactionConfig"); + } + this.transactionConfigClass = transactionConfigClass; + } + + /** + * Set properties to be passed to the TransactionConfig instance used + * by this SqlMapClient. Supported properties depend on the concrete + * TransactionConfig implementation used: + *
The default implementation uses the standard iBATIS {@link SqlMapClientBuilder}
+ * API to build a SqlMapClient instance based on an InputStream (if possible,
+ * on iBATIS 2.3 and higher) or on a Reader (on iBATIS up to version 2.2).
+ * @param configLocations the config files to load from
+ * @param properties the SqlMapClient properties (if any)
+ * @return the SqlMapClient instance (never null)
+ * @throws IOException if loading the config file failed
+ * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient
+ */
+ protected SqlMapClient buildSqlMapClient(
+ Resource[] configLocations, Resource[] mappingLocations, Properties properties)
+ throws IOException {
+
+ if (ObjectUtils.isEmpty(configLocations)) {
+ throw new IllegalArgumentException("At least 1 'configLocation' entry is required");
+ }
+
+ SqlMapClient client = null;
+ SqlMapConfigParser configParser = new SqlMapConfigParser();
+ for (int i = 0; i < configLocations.length; i++) {
+ InputStream is = configLocations[i].getInputStream();
+ try {
+ client = configParser.parse(is, properties);
+ }
+ catch (RuntimeException ex) {
+ throw new NestedIOException("Failed to parse config resource: " + configLocations[i], ex.getCause());
+ }
+ }
+
+ if (mappingLocations != null) {
+ SqlMapParser mapParser = SqlMapParserFactory.createSqlMapParser(configParser);
+ for (int i = 0; i < mappingLocations.length; i++) {
+ try {
+ mapParser.parse(mappingLocations[i].getInputStream());
+ }
+ catch (NodeletException ex) {
+ throw new NestedIOException("Failed to parse mapping resource: " + mappingLocations[i], ex);
+ }
+ }
+ }
+
+ return client;
+ }
+
+ /**
+ * Apply the given iBATIS TransactionConfig to the SqlMapClient.
+ *
The default implementation casts to ExtendedSqlMapClient, retrieves the maximum + * number of concurrent transactions from the SqlMapExecutorDelegate, and sets + * an iBATIS TransactionManager with the given TransactionConfig. + * @param sqlMapClient the SqlMapClient to apply the TransactionConfig to + * @param transactionConfig the iBATIS TransactionConfig to apply + * @see com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient + * @see com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate#getMaxTransactions + * @see com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate#setTxManager + */ + protected void applyTransactionConfig(SqlMapClient sqlMapClient, TransactionConfig transactionConfig) { + if (!(sqlMapClient instanceof ExtendedSqlMapClient)) { + throw new IllegalArgumentException( + "Cannot set TransactionConfig with DataSource for SqlMapClient if not of type " + + "ExtendedSqlMapClient: " + sqlMapClient); + } + ExtendedSqlMapClient extendedClient = (ExtendedSqlMapClient) sqlMapClient; + transactionConfig.setMaximumConcurrentTransactions(extendedClient.getDelegate().getMaxTransactions()); + extendedClient.getDelegate().setTxManager(new TransactionManager(transactionConfig)); + } + + + public Object getObject() { + return this.sqlMapClient; + } + + public Class getObjectType() { + return (this.sqlMapClient != null ? this.sqlMapClient.getClass() : SqlMapClient.class); + } + + public boolean isSingleton() { + return true; + } + + + /** + * Inner class to avoid hard-coded iBATIS 2.3.2 dependency (XmlParserState class). + */ + private static class SqlMapParserFactory { + + public static SqlMapParser createSqlMapParser(SqlMapConfigParser configParser) { + // Ideally: XmlParserState state = configParser.getState(); + // Should raise an enhancement request with iBATIS... + XmlParserState state = null; + try { + Field stateField = SqlMapConfigParser.class.getDeclaredField("state"); + stateField.setAccessible(true); + state = (XmlParserState) stateField.get(configParser); + } + catch (Exception ex) { + throw new IllegalStateException("iBATIS 2.3.2 'state' field not found in SqlMapConfigParser class - " + + "please upgrade to IBATIS 2.3.2 or higher in order to use the new 'mappingLocations' feature. " + ex); + } + return new SqlMapParser(state); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientOperations.java new file mode 100644 index 00000000000..06ddb42c790 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientOperations.java @@ -0,0 +1,198 @@ +/* + * Copyright 2002-2006 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.ibatis; + +import java.util.List; +import java.util.Map; + +import com.ibatis.common.util.PaginatedList; +import com.ibatis.sqlmap.client.event.RowHandler; + +import org.springframework.dao.DataAccessException; + +/** + * Interface that specifies a basic set of iBATIS SqlMapClient operations, + * implemented by {@link SqlMapClientTemplate}. Not often used, but a useful + * option to enhance testability, as it can easily be mocked or stubbed. + * + *
Defines SqlMapClientTemplate's convenience methods that mirror
+ * the iBATIS {@link com.ibatis.sqlmap.client.SqlMapExecutor}'s execution
+ * methods. Users are strongly encouraged to read the iBATIS javadocs
+ * for details on the semantics of those methods.
+ *
+ * @author Juergen Hoeller
+ * @since 24.02.2004
+ * @see SqlMapClientTemplate
+ * @see com.ibatis.sqlmap.client.SqlMapClient
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor
+ */
+public interface SqlMapClientOperations {
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForObject(String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Object queryForObject(String statementName) throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForObject(String, Object)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Object queryForObject(String statementName, Object parameterObject)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForObject(String, Object, Object)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Object queryForObject(String statementName, Object parameterObject, Object resultObject)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForList(String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ List queryForList(String statementName) throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForList(String, Object)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ List queryForList(String statementName, Object parameterObject)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForList(String, int, int)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ List queryForList(String statementName, int skipResults, int maxResults)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForList(String, Object, int, int)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ List queryForList(String statementName, Object parameterObject, int skipResults, int maxResults)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryWithRowHandler(String, RowHandler)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ void queryWithRowHandler(String statementName, RowHandler rowHandler)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryWithRowHandler(String, Object, RowHandler)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ void queryWithRowHandler(String statementName, Object parameterObject, RowHandler rowHandler)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForPaginatedList(String, int)
+ * @deprecated as of iBATIS 2.3.0
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ PaginatedList queryForPaginatedList(String statementName, int pageSize)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForPaginatedList(String, Object, int)
+ * @deprecated as of iBATIS 2.3.0
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ PaginatedList queryForPaginatedList(String statementName, Object parameterObject, int pageSize)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForMap(String, Object, String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Map queryForMap(String statementName, Object parameterObject, String keyProperty)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#queryForMap(String, Object, String, String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Map queryForMap(String statementName, Object parameterObject, String keyProperty, String valueProperty)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#insert(String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Object insert(String statementName) throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#insert(String, Object)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ Object insert(String statementName, Object parameterObject) throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#update(String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ int update(String statementName) throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#update(String, Object)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ int update(String statementName, Object parameterObject) throws DataAccessException;
+
+ /**
+ * Convenience method provided by Spring: execute an update operation
+ * with an automatic check that the update affected the given required
+ * number of rows.
+ * @param statementName the name of the mapped statement
+ * @param parameterObject the parameter object
+ * @param requiredRowsAffected the number of rows that the update is
+ * required to affect
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ void update(String statementName, Object parameterObject, int requiredRowsAffected)
+ throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#delete(String)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ int delete(String statementName) throws DataAccessException;
+
+ /**
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor#delete(String, Object)
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ int delete(String statementName, Object parameterObject) throws DataAccessException;
+
+ /**
+ * Convenience method provided by Spring: execute a delete operation
+ * with an automatic check that the delete affected the given required
+ * number of rows.
+ * @param statementName the name of the mapped statement
+ * @param parameterObject the parameter object
+ * @param requiredRowsAffected the number of rows that the delete is
+ * required to affect
+ * @throws org.springframework.dao.DataAccessException in case of errors
+ */
+ void delete(String statementName, Object parameterObject, int requiredRowsAffected)
+ throws DataAccessException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientTemplate.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientTemplate.java
new file mode 100644
index 00000000000..27671dfdaa2
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/SqlMapClientTemplate.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright 2002-2007 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.ibatis;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import com.ibatis.common.util.PaginatedList;
+import com.ibatis.sqlmap.client.SqlMapClient;
+import com.ibatis.sqlmap.client.SqlMapExecutor;
+import com.ibatis.sqlmap.client.SqlMapSession;
+import com.ibatis.sqlmap.client.event.RowHandler;
+import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.CannotGetJdbcConnectionException;
+import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
+import org.springframework.jdbc.support.JdbcAccessor;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class that simplifies data access via the iBATIS
+ * {@link com.ibatis.sqlmap.client.SqlMapClient} API, converting checked
+ * SQLExceptions into unchecked DataAccessExceptions, following the
+ * org.springframework.dao exception hierarchy.
+ * Uses the same {@link org.springframework.jdbc.support.SQLExceptionTranslator}
+ * mechanism as {@link org.springframework.jdbc.core.JdbcTemplate}.
+ *
+ *
The main method of this class executes a callback that implements a + * data access action. Furthermore, this class provides numerous convenience + * methods that mirror {@link com.ibatis.sqlmap.client.SqlMapExecutor}'s + * execution methods. + * + *
It is generally recommended to use the convenience methods on this template + * for plain query/insert/update/delete operations. However, for more complex + * operations like batch updates, a custom SqlMapClientCallback must be implemented, + * usually as anonymous inner class. For example: + * + *
+ * getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
+ * public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ * executor.startBatch();
+ * executor.update("insertSomething", "myParamValue");
+ * executor.update("insertSomethingElse", "myOtherParamValue");
+ * executor.executeBatch();
+ * return null;
+ * }
+ * });
+ *
+ * The template needs a SqlMapClient to work on, passed in via the "sqlMapClient"
+ * property. A Spring context typically uses a {@link SqlMapClientFactoryBean}
+ * to build the SqlMapClient. The template an additionally be configured with a
+ * DataSource for fetching Connections, although this is not necessary if a
+ * DataSource is specified for the SqlMapClient itself (typically through
+ * SqlMapClientFactoryBean's "dataSource" property).
+ *
+ * @author Juergen Hoeller
+ * @since 24.02.2004
+ * @see #execute
+ * @see #setSqlMapClient
+ * @see #setDataSource
+ * @see #setExceptionTranslator
+ * @see SqlMapClientFactoryBean#setDataSource
+ * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource
+ * @see com.ibatis.sqlmap.client.SqlMapExecutor
+ */
+public class SqlMapClientTemplate extends JdbcAccessor implements SqlMapClientOperations {
+
+ private SqlMapClient sqlMapClient;
+
+ private boolean lazyLoadingAvailable = true;
+
+
+ /**
+ * Create a new SqlMapClientTemplate.
+ */
+ public SqlMapClientTemplate() {
+ }
+
+ /**
+ * Create a new SqlMapTemplate.
+ * @param sqlMapClient iBATIS SqlMapClient that defines the mapped statements
+ */
+ public SqlMapClientTemplate(SqlMapClient sqlMapClient) {
+ setSqlMapClient(sqlMapClient);
+ afterPropertiesSet();
+ }
+
+ /**
+ * Create a new SqlMapTemplate.
+ * @param dataSource JDBC DataSource to obtain connections from
+ * @param sqlMapClient iBATIS SqlMapClient that defines the mapped statements
+ */
+ public SqlMapClientTemplate(DataSource dataSource, SqlMapClient sqlMapClient) {
+ setDataSource(dataSource);
+ setSqlMapClient(sqlMapClient);
+ afterPropertiesSet();
+ }
+
+
+ /**
+ * Set the iBATIS Database Layer SqlMapClient that defines the mapped statements.
+ */
+ public void setSqlMapClient(SqlMapClient sqlMapClient) {
+ this.sqlMapClient = sqlMapClient;
+ }
+
+ /**
+ * Return the iBATIS Database Layer SqlMapClient that this template works with.
+ */
+ public SqlMapClient getSqlMapClient() {
+ return this.sqlMapClient;
+ }
+
+ /**
+ * If no DataSource specified, use SqlMapClient's DataSource.
+ * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource()
+ */
+ public DataSource getDataSource() {
+ DataSource ds = super.getDataSource();
+ return (ds != null ? ds : this.sqlMapClient.getDataSource());
+ }
+
+ public void afterPropertiesSet() {
+ if (this.sqlMapClient == null) {
+ throw new IllegalArgumentException("Property 'sqlMapClient' is required");
+ }
+ if (this.sqlMapClient instanceof ExtendedSqlMapClient) {
+ // Check whether iBATIS lazy loading is available, that is,
+ // whether a DataSource was specified on the SqlMapClient itself.
+ this.lazyLoadingAvailable = (((ExtendedSqlMapClient) this.sqlMapClient).getDelegate().getTxManager() != null);
+ }
+ super.afterPropertiesSet();
+ }
+
+
+ /**
+ * Execute the given data access action on a SqlMapExecutor.
+ * @param action callback object that specifies the data access action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException in case of SQL Maps errors
+ */
+ public Object execute(SqlMapClientCallback action) throws DataAccessException {
+ Assert.notNull(action, "Callback object must not be null");
+ Assert.notNull(this.sqlMapClient, "No SqlMapClient specified");
+
+ // We always needs to use a SqlMapSession, as we need to pass a Spring-managed
+ // Connection (potentially transactional) in. This shouldn't be necessary if
+ // we run against a TransactionAwareDataSourceProxy underneath, but unfortunately
+ // we still need it to make iBATIS batch execution work properly: If iBATIS
+ // doesn't recognize an existing transaction, it automatically executes the
+ // batch for every single statement...
+
+ SqlMapSession session = this.sqlMapClient.openSession();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
+ }
+ Connection ibatisCon = null;
+
+ try {
+ Connection springCon = null;
+ DataSource dataSource = getDataSource();
+ boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);
+
+ // Obtain JDBC Connection to operate on...
+ try {
+ ibatisCon = session.getCurrentConnection();
+ if (ibatisCon == null) {
+ springCon = (transactionAware ?
+ dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
+ session.setUserConnection(springCon);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation");
+ }
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation");
+ }
+ }
+ }
+ catch (SQLException ex) {
+ throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
+ }
+
+ // Execute given callback...
+ try {
+ return action.doInSqlMapClient(session);
+ }
+ catch (SQLException ex) {
+ throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
+ }
+ finally {
+ try {
+ if (springCon != null) {
+ if (transactionAware) {
+ springCon.close();
+ }
+ else {
+ DataSourceUtils.doReleaseConnection(springCon, dataSource);
+ }
+ }
+ }
+ catch (Throwable ex) {
+ logger.debug("Could not close JDBC Connection", ex);
+ }
+ }
+
+ // Processing finished - potentially session still to be closed.
+ }
+ finally {
+ // Only close SqlMapSession if we know we've actually opened it
+ // at the present level.
+ if (ibatisCon == null) {
+ session.close();
+ }
+ }
+ }
+
+ /**
+ * Execute the given data access action on a SqlMapExecutor,
+ * expecting a List result.
+ * @param action callback object that specifies the data access action
+ * @return the List result
+ * @throws DataAccessException in case of SQL Maps errors
+ */
+ public List executeWithListResult(SqlMapClientCallback action) throws DataAccessException {
+ return (List) execute(action);
+ }
+
+ /**
+ * Execute the given data access action on a SqlMapExecutor,
+ * expecting a Map result.
+ * @param action callback object that specifies the data access action
+ * @return the Map result
+ * @throws DataAccessException in case of SQL Maps errors
+ */
+ public Map executeWithMapResult(SqlMapClientCallback action) throws DataAccessException {
+ return (Map) execute(action);
+ }
+
+
+ public Object queryForObject(String statementName) throws DataAccessException {
+ return queryForObject(statementName, null);
+ }
+
+ public Object queryForObject(final String statementName, final Object parameterObject)
+ throws DataAccessException {
+
+ return execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForObject(statementName, parameterObject);
+ }
+ });
+ }
+
+ public Object queryForObject(
+ final String statementName, final Object parameterObject, final Object resultObject)
+ throws DataAccessException {
+
+ return execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForObject(statementName, parameterObject, resultObject);
+ }
+ });
+ }
+
+ public List queryForList(String statementName) throws DataAccessException {
+ return queryForList(statementName, null);
+ }
+
+ public List queryForList(final String statementName, final Object parameterObject)
+ throws DataAccessException {
+
+ return executeWithListResult(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForList(statementName, parameterObject);
+ }
+ });
+ }
+
+ public List queryForList(String statementName, int skipResults, int maxResults)
+ throws DataAccessException {
+
+ return queryForList(statementName, null, skipResults, maxResults);
+ }
+
+ public List queryForList(
+ final String statementName, final Object parameterObject, final int skipResults, final int maxResults)
+ throws DataAccessException {
+
+ return executeWithListResult(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForList(statementName, parameterObject, skipResults, maxResults);
+ }
+ });
+ }
+
+ public void queryWithRowHandler(String statementName, RowHandler rowHandler)
+ throws DataAccessException {
+
+ queryWithRowHandler(statementName, null, rowHandler);
+ }
+
+ public void queryWithRowHandler(
+ final String statementName, final Object parameterObject, final RowHandler rowHandler)
+ throws DataAccessException {
+
+ execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ executor.queryWithRowHandler(statementName, parameterObject, rowHandler);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * @deprecated as of iBATIS 2.3.0
+ */
+ public PaginatedList queryForPaginatedList(String statementName, int pageSize)
+ throws DataAccessException {
+
+ return queryForPaginatedList(statementName, null, pageSize);
+ }
+
+ /**
+ * @deprecated as of iBATIS 2.3.0
+ */
+ public PaginatedList queryForPaginatedList(
+ final String statementName, final Object parameterObject, final int pageSize)
+ throws DataAccessException {
+
+ // Throw exception if lazy loading will not work.
+ if (!this.lazyLoadingAvailable) {
+ throw new InvalidDataAccessApiUsageException(
+ "SqlMapClient needs to have DataSource to allow for lazy loading" +
+ " - specify SqlMapClientFactoryBean's 'dataSource' property");
+ }
+
+ return (PaginatedList) execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForPaginatedList(statementName, parameterObject, pageSize);
+ }
+ });
+ }
+
+ public Map queryForMap(
+ final String statementName, final Object parameterObject, final String keyProperty)
+ throws DataAccessException {
+
+ return executeWithMapResult(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForMap(statementName, parameterObject, keyProperty);
+ }
+ });
+ }
+
+ public Map queryForMap(
+ final String statementName, final Object parameterObject, final String keyProperty, final String valueProperty)
+ throws DataAccessException {
+
+ return executeWithMapResult(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.queryForMap(statementName, parameterObject, keyProperty, valueProperty);
+ }
+ });
+ }
+
+ public Object insert(String statementName) throws DataAccessException {
+ return insert(statementName, null);
+ }
+
+ public Object insert(final String statementName, final Object parameterObject)
+ throws DataAccessException {
+
+ return execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return executor.insert(statementName, parameterObject);
+ }
+ });
+ }
+
+ public int update(String statementName) throws DataAccessException {
+ return update(statementName, null);
+ }
+
+ public int update(final String statementName, final Object parameterObject)
+ throws DataAccessException {
+
+ Integer result = (Integer) execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return new Integer(executor.update(statementName, parameterObject));
+ }
+ });
+ return result.intValue();
+ }
+
+ public void update(String statementName, Object parameterObject, int requiredRowsAffected)
+ throws DataAccessException {
+
+ int actualRowsAffected = update(statementName, parameterObject);
+ if (actualRowsAffected != requiredRowsAffected) {
+ throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(
+ statementName, requiredRowsAffected, actualRowsAffected);
+ }
+ }
+
+ public int delete(String statementName) throws DataAccessException {
+ return delete(statementName, null);
+ }
+
+ public int delete(final String statementName, final Object parameterObject)
+ throws DataAccessException {
+
+ Integer result = (Integer) execute(new SqlMapClientCallback() {
+ public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
+ return new Integer(executor.delete(statementName, parameterObject));
+ }
+ });
+ return result.intValue();
+ }
+
+ public void delete(String statementName, Object parameterObject, int requiredRowsAffected)
+ throws DataAccessException {
+
+ int actualRowsAffected = delete(statementName, parameterObject);
+ if (actualRowsAffected != requiredRowsAffected) {
+ throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(
+ statementName, requiredRowsAffected, actualRowsAffected);
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/package.html
new file mode 100644
index 00000000000..ebae95b87d1
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/package.html
@@ -0,0 +1,12 @@
+
+
+
+Package providing integration of
+iBATIS Database Layer
+with Spring concepts.
+
+Contains resource helper classes and template classes for +data access with the iBATIS SqlMapClient API. + + + diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/AbstractLobTypeHandler.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/AbstractLobTypeHandler.java new file mode 100644 index 00000000000..53e8922340c --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/AbstractLobTypeHandler.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2005 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.ibatis.support; + +import java.io.IOException; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.ibatis.sqlmap.engine.type.BaseTypeHandler; + +import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.orm.ibatis.SqlMapClientFactoryBean; +import org.springframework.transaction.support.TransactionSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Abstract base class for iBATIS TypeHandler implementations that map to LOBs. + * Retrieves the LobHandler to use from SqlMapClientFactoryBean at config time. + * + *
For writing LOBs, an active Spring transaction synchronization is required, + * to be able to register a synchronization that closes the LobCreator. + * + *
Offers template methods for setting parameters and getting result values, + * passing in the LobHandler or LobCreator to use. + * + * @author Juergen Hoeller + * @since 1.1.5 + * @see org.springframework.jdbc.support.lob.LobHandler + * @see org.springframework.jdbc.support.lob.LobCreator + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#setLobHandler + */ +public abstract class AbstractLobTypeHandler extends BaseTypeHandler { + + /** + * Order value for TransactionSynchronization objects that clean up LobCreators. + * Return DataSourceUtils.#CONNECTION_SYNCHRONIZATION_ORDER - 100 to execute + * LobCreator cleanup before JDBC Connection cleanup, if any. + * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER + */ + public static final int LOB_CREATOR_SYNCHRONIZATION_ORDER = + DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 200; + + private LobHandler lobHandler; + + + /** + * Constructor used by iBATIS: fetches config-time LobHandler from + * SqlMapClientFactoryBean. + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#getConfigTimeLobHandler + */ + public AbstractLobTypeHandler() { + this(SqlMapClientFactoryBean.getConfigTimeLobHandler()); + } + + /** + * Constructor used for testing: takes an explicit LobHandler. + */ + protected AbstractLobTypeHandler(LobHandler lobHandler) { + if (lobHandler == null) { + throw new IllegalStateException("No LobHandler found for configuration - " + + "lobHandler property must be set on SqlMapClientFactoryBean"); + } + this.lobHandler = lobHandler; + } + + + /** + * This implementation delegates to setParameterInternal, + * passing in a transaction-synchronized LobCreator for the + * LobHandler of this type. + * @see #setParameterInternal + */ + public final void setParameter(PreparedStatement ps, int i, Object parameter, String jdbcType) + throws SQLException { + + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + throw new IllegalStateException("Spring transaction synchronization needs to be active for " + + "setting values in iBATIS TypeHandlers that delegate to a Spring LobHandler"); + } + final LobCreator lobCreator = this.lobHandler.getLobCreator(); + try { + setParameterInternal(ps, i, parameter, jdbcType, lobCreator); + } + catch (IOException ex) { + throw new SQLException("I/O errors during LOB access: " + ex.getMessage()); + } + + TransactionSynchronizationManager.registerSynchronization( + new LobCreatorSynchronization(lobCreator)); + } + + /** + * This implementation delegates to the getResult version + * that takes a column index. + * @see #getResult(java.sql.ResultSet, String) + * @see java.sql.ResultSet#findColumn + */ + public final Object getResult(ResultSet rs, String columnName) throws SQLException { + return getResult(rs, rs.findColumn(columnName)); + } + + /** + * This implementation delegates to getResultInternal, + * passing in the LobHandler of this type. + * @see #getResultInternal + */ + public final Object getResult(ResultSet rs, int columnIndex) throws SQLException { + try { + return getResultInternal(rs, columnIndex, this.lobHandler); + } + catch (IOException ex) { + throw new SQLException( + "I/O errors during LOB access: " + ex.getClass().getName() + ": " + ex.getMessage()); + } + } + + /** + * This implementation always throws a SQLException: + * retrieving LOBs from a CallableStatement is not supported. + */ + public Object getResult(CallableStatement cs, int columnIndex) throws SQLException { + throw new SQLException("Retrieving LOBs from a CallableStatement is not supported"); + } + + + /** + * Template method to set the given value on the given statement. + * @param ps the PreparedStatement to set on + * @param index the statement parameter index + * @param value the parameter value to set + * @param jdbcType the JDBC type of the parameter + * @param lobCreator the LobCreator to use + * @throws SQLException if thrown by JDBC methods + * @throws IOException if thrown by streaming methods + */ + protected abstract void setParameterInternal( + PreparedStatement ps, int index, Object value, String jdbcType, LobCreator lobCreator) + throws SQLException, IOException; + + /** + * Template method to extract a value from the given result set. + * @param rs the ResultSet to extract from + * @param index the index in the ResultSet + * @param lobHandler the LobHandler to use + * @return the extracted value + * @throws SQLException if thrown by JDBC methods + * @throws IOException if thrown by streaming methods + */ + protected abstract Object getResultInternal(ResultSet rs, int index, LobHandler lobHandler) + throws SQLException, IOException; + + + /** + * Callback for resource cleanup at the end of a Spring transaction. + * Invokes LobCreator.close to clean up temporary LOBs that might have been created. + * @see org.springframework.jdbc.support.lob.LobCreator#close + */ + private static class LobCreatorSynchronization extends TransactionSynchronizationAdapter { + + private final LobCreator lobCreator; + + public LobCreatorSynchronization(LobCreator lobCreator) { + this.lobCreator = lobCreator; + } + + public int getOrder() { + return LOB_CREATOR_SYNCHRONIZATION_ORDER; + } + + public void beforeCompletion() { + this.lobCreator.close(); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/BlobByteArrayTypeHandler.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/BlobByteArrayTypeHandler.java new file mode 100644 index 00000000000..589ce14a7ef --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/BlobByteArrayTypeHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2005 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.ibatis.support; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.jdbc.support.lob.LobHandler; + +/** + * iBATIS TypeHandler implementation for byte arrays that get mapped to BLOBs. + * Retrieves the LobHandler to use from SqlMapClientFactoryBean at config time. + * + *
Can also be defined in generic iBATIS mappings, as DefaultLobCreator will + * work with most JDBC-compliant database drivers. In this case, the field type + * does not have to be BLOB: For databases like MySQL and MS SQL Server, any + * large enough binary type will work. + * + * @author Juergen Hoeller + * @since 1.1.5 + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#setLobHandler + */ +public class BlobByteArrayTypeHandler extends AbstractLobTypeHandler { + + /** + * Constructor used by iBATIS: fetches config-time LobHandler from + * SqlMapClientFactoryBean. + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#getConfigTimeLobHandler + */ + public BlobByteArrayTypeHandler() { + super(); + } + + /** + * Constructor used for testing: takes an explicit LobHandler. + */ + protected BlobByteArrayTypeHandler(LobHandler lobHandler) { + super(lobHandler); + } + + protected void setParameterInternal( + PreparedStatement ps, int index, Object value, String jdbcType, LobCreator lobCreator) + throws SQLException { + lobCreator.setBlobAsBytes(ps, index, (byte[]) value); + } + + protected Object getResultInternal(ResultSet rs, int index, LobHandler lobHandler) + throws SQLException { + return lobHandler.getBlobAsBytes(rs, index); + } + + public Object valueOf(String s) { + return s.getBytes(); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/BlobSerializableTypeHandler.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/BlobSerializableTypeHandler.java new file mode 100644 index 00000000000..f392d521a55 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/BlobSerializableTypeHandler.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2005 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.ibatis.support; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.jdbc.support.lob.LobHandler; + +/** + * iBATIS TypeHandler implementation for arbitrary objects that get serialized to BLOBs. + * Retrieves the LobHandler to use from SqlMapClientFactoryBean at config time. + * + *
Can also be defined in generic iBATIS mappings, as DefaultLobCreator will + * work with most JDBC-compliant database drivers. In this case, the field type + * does not have to be BLOB: For databases like MySQL and MS SQL Server, any + * large enough binary type will work. + * + * @author Juergen Hoeller + * @since 1.1.5 + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#setLobHandler + */ +public class BlobSerializableTypeHandler extends AbstractLobTypeHandler { + + /** + * Constructor used by iBATIS: fetches config-time LobHandler from + * SqlMapClientFactoryBean. + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#getConfigTimeLobHandler + */ + public BlobSerializableTypeHandler() { + super(); + } + + /** + * Constructor used for testing: takes an explicit LobHandler. + */ + protected BlobSerializableTypeHandler(LobHandler lobHandler) { + super(lobHandler); + } + + protected void setParameterInternal( + PreparedStatement ps, int index, Object value, String jdbcType, LobCreator lobCreator) + throws SQLException, IOException { + + if (value != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + try { + oos.writeObject(value); + oos.flush(); + lobCreator.setBlobAsBytes(ps, index, baos.toByteArray()); + } + finally { + oos.close(); + } + } + else { + lobCreator.setBlobAsBytes(ps, index, null); + } + } + + protected Object getResultInternal(ResultSet rs, int index, LobHandler lobHandler) + throws SQLException, IOException { + + InputStream is = lobHandler.getBlobAsBinaryStream(rs, index); + if (is != null) { + ObjectInputStream ois = new ObjectInputStream(is); + try { + return ois.readObject(); + } + catch (ClassNotFoundException ex) { + throw new SQLException("Could not deserialize BLOB contents: " + ex.getMessage()); + } + finally { + ois.close(); + } + } + else { + return null; + } + } + + public Object valueOf(String s) { + return s; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/ClobStringTypeHandler.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/ClobStringTypeHandler.java new file mode 100644 index 00000000000..e4fe32c1680 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/ClobStringTypeHandler.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2005 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.ibatis.support; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.jdbc.support.lob.LobHandler; + +/** + * iBATIS TypeHandler implementation for Strings that get mapped to CLOBs. + * Retrieves the LobHandler to use from SqlMapClientFactoryBean at config time. + * + *
Particularly useful for storing Strings with more than 4000 characters in an + * Oracle database (only possible via CLOBs), in combination with OracleLobHandler. + * + *
Can also be defined in generic iBATIS mappings, as DefaultLobCreator will + * work with most JDBC-compliant database drivers. In this case, the field type + * does not have to be BLOB: For databases like MySQL and MS SQL Server, any + * large enough binary type will work. + * + * @author Juergen Hoeller + * @since 1.1.5 + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#setLobHandler + */ +public class ClobStringTypeHandler extends AbstractLobTypeHandler { + + /** + * Constructor used by iBATIS: fetches config-time LobHandler from + * SqlMapClientFactoryBean. + * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#getConfigTimeLobHandler + */ + public ClobStringTypeHandler() { + super(); + } + + /** + * Constructor used for testing: takes an explicit LobHandler. + */ + protected ClobStringTypeHandler(LobHandler lobHandler) { + super(lobHandler); + } + + protected void setParameterInternal( + PreparedStatement ps, int index, Object value, String jdbcType, LobCreator lobCreator) + throws SQLException { + lobCreator.setClobAsString(ps, index, (String) value); + } + + protected Object getResultInternal(ResultSet rs, int index, LobHandler lobHandler) + throws SQLException { + return lobHandler.getClobAsString(rs, index); + } + + public Object valueOf(String s) { + return s; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/SqlMapClientDaoSupport.java b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/SqlMapClientDaoSupport.java new file mode 100644 index 00000000000..195b129b925 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/SqlMapClientDaoSupport.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.ibatis.support; + +import javax.sql.DataSource; + +import com.ibatis.sqlmap.client.SqlMapClient; + +import org.springframework.dao.support.DaoSupport; +import org.springframework.orm.ibatis.SqlMapClientTemplate; +import org.springframework.util.Assert; + +/** + * Convenient super class for iBATIS SqlMapClient data access objects. + * Requires a SqlMapClient to be set, providing a SqlMapClientTemplate + * based on it to subclasses. + * + *
Instead of a plain SqlMapClient, you can also pass a preconfigured + * SqlMapClientTemplate instance in. This allows you to share your + * SqlMapClientTemplate configuration for all your DAOs, for example + * a custom SQLExceptionTranslator to use. + * + * @author Juergen Hoeller + * @since 24.02.2004 + * @see #setSqlMapClient + * @see #setSqlMapClientTemplate + * @see org.springframework.orm.ibatis.SqlMapClientTemplate + * @see org.springframework.orm.ibatis.SqlMapClientTemplate#setExceptionTranslator + */ +public abstract class SqlMapClientDaoSupport extends DaoSupport { + + private SqlMapClientTemplate sqlMapClientTemplate = new SqlMapClientTemplate(); + + private boolean externalTemplate = false; + + + /** + * Set the JDBC DataSource to be used by this DAO. + * Not required: The SqlMapClient might carry a shared DataSource. + * @see #setSqlMapClient + */ + public final void setDataSource(DataSource dataSource) { + if (!this.externalTemplate) { + this.sqlMapClientTemplate.setDataSource(dataSource); + } + } + + /** + * Return the JDBC DataSource used by this DAO. + */ + public final DataSource getDataSource() { + return this.sqlMapClientTemplate.getDataSource(); + } + + /** + * Set the iBATIS Database Layer SqlMapClient to work with. + * Either this or a "sqlMapClientTemplate" is required. + * @see #setSqlMapClientTemplate + */ + public final void setSqlMapClient(SqlMapClient sqlMapClient) { + if (!this.externalTemplate) { + this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient); + } + } + + /** + * Return the iBATIS Database Layer SqlMapClient that this template works with. + */ + public final SqlMapClient getSqlMapClient() { + return this.sqlMapClientTemplate.getSqlMapClient(); + } + + /** + * Set the SqlMapClientTemplate for this DAO explicitly, + * as an alternative to specifying a SqlMapClient. + * @see #setSqlMapClient + */ + public final void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) { + Assert.notNull(sqlMapClientTemplate, "SqlMapClientTemplate must not be null"); + this.sqlMapClientTemplate = sqlMapClientTemplate; + this.externalTemplate = true; + } + + /** + * Return the SqlMapClientTemplate for this DAO, + * pre-initialized with the SqlMapClient or set explicitly. + */ + public final SqlMapClientTemplate getSqlMapClientTemplate() { + return this.sqlMapClientTemplate; + } + + protected final void checkDaoConfig() { + if (!this.externalTemplate) { + this.sqlMapClientTemplate.afterPropertiesSet(); + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/package.html new file mode 100644 index 00000000000..ad799b74950 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/ibatis/support/package.html @@ -0,0 +1,8 @@ + +
+ +Classes supporting theorg.springframework.orm.ibatis package.
+Contains a DAO base class for SqlMapClientTemplate usage.
+
+
+
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java
new file mode 100644
index 00000000000..267613d619a
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2002-2007 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.jdo;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.jdo.JDOException;
+import javax.jdo.PersistenceManager;
+import javax.jdo.Query;
+import javax.jdo.Transaction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.jdbc.datasource.ConnectionHandle;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.jdbc.support.SQLExceptionTranslator;
+import org.springframework.transaction.InvalidIsolationLevelException;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+
+/**
+ * Default implementation of the {@link JdoDialect} interface.
+ * Updated to build on JDO 2.0 or higher, as of Spring 2.5.
+ * Used as default dialect by {@link JdoAccessor} and {@link JdoTransactionManager}.
+ *
+ * Simply begins a standard JDO transaction in beginTransaction.
+ * Returns a handle for a JDO2 DataStoreConnection on getJdbcConnection.
+ * Calls the corresponding JDO2 PersistenceManager operation on flush
+ * Ignores a given query timeout in applyQueryTimeout.
+ * Uses a Spring SQLExceptionTranslator for exception translation, if applicable.
+ *
+ *
Note that, even with JDO2, vendor-specific subclasses are still necessary
+ * for special transaction semantics and more sophisticated exception translation.
+ * Furthermore, vendor-specific subclasses are encouraged to expose the native JDBC
+ * Connection on getJdbcConnection, rather than JDO2's wrapper handle.
+ *
+ *
This class also implements the PersistenceExceptionTranslator interface, + * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor, + * for AOP-based translation of native exceptions to Spring DataAccessExceptions. + * Hence, the presence of a standard DefaultJdoDialect bean automatically enables + * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions. + * + * @author Juergen Hoeller + * @since 1.1 + * @see #setJdbcExceptionTranslator + * @see JdoAccessor#setJdoDialect + * @see JdoTransactionManager#setJdoDialect + * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor + */ +public class DefaultJdoDialect implements JdoDialect, PersistenceExceptionTranslator { + + protected final Log logger = LogFactory.getLog(getClass()); + + private SQLExceptionTranslator jdbcExceptionTranslator; + + + /** + * Create a new DefaultJdoDialect. + */ + public DefaultJdoDialect() { + } + + /** + * Create a new DefaultJdoDialect. + * @param connectionFactory the connection factory of the JDO PersistenceManagerFactory, + * which is used to initialize the default JDBC exception translator + * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory() + * @see PersistenceManagerFactoryUtils#newJdbcExceptionTranslator(Object) + */ + DefaultJdoDialect(Object connectionFactory) { + this.jdbcExceptionTranslator = PersistenceManagerFactoryUtils.newJdbcExceptionTranslator(connectionFactory); + } + + /** + * Set the JDBC exception translator for this dialect. + *
Applied to any SQLException root cause of a JDOException, if specified.
+ * The default is to rely on the JDO provider's native exception translation.
+ * @param jdbcExceptionTranslator exception translator
+ * @see java.sql.SQLException
+ * @see javax.jdo.JDOException#getCause()
+ * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
+ * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
+ */
+ public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
+ this.jdbcExceptionTranslator = jdbcExceptionTranslator;
+ }
+
+ /**
+ * Return the JDBC exception translator for this dialect, if any.
+ */
+ public SQLExceptionTranslator getJdbcExceptionTranslator() {
+ return this.jdbcExceptionTranslator;
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Hooks for transaction management (used by JdoTransactionManager)
+ //-------------------------------------------------------------------------
+
+ /**
+ * This implementation invokes the standard JDO Transaction.begin
+ * method. Throws an InvalidIsolationLevelException if a non-default isolation
+ * level is set.
+ * @see javax.jdo.Transaction#begin
+ * @see org.springframework.transaction.InvalidIsolationLevelException
+ */
+ public Object beginTransaction(Transaction transaction, TransactionDefinition definition)
+ throws JDOException, SQLException, TransactionException {
+
+ if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
+ throw new InvalidIsolationLevelException(
+ "Standard JDO does not support custom isolation levels: " +
+ "use a special JdoDialect implementation for your JDO provider");
+ }
+ transaction.begin();
+ return null;
+ }
+
+ /**
+ * This implementation does nothing, as the default beginTransaction implementation
+ * does not require any cleanup.
+ * @see #beginTransaction
+ */
+ public void cleanupTransaction(Object transactionData) {
+ }
+
+ /**
+ * This implementation returns a DataStoreConnectionHandle for JDO2,
+ * which will also work on JDO1 until actually accessing the JDBC Connection.
+ *
For pre-JDO2 implementations, override this method to return the
+ * Connection through the corresponding vendor-specific mechanism, or null
+ * if the Connection is not retrievable.
+ *
NOTE: A JDO2 DataStoreConnection is always a wrapper, + * never the native JDBC Connection. If you need access to the native JDBC + * Connection (or the connection pool handle, to be unwrapped via a Spring + * NativeJdbcExtractor), override this method to return the native + * Connection through the corresponding vendor-specific mechanism. + *
A JDO2 DataStoreConnection is only "borrowed" from the PersistenceManager:
+ * it needs to be returned as early as possible. Effectively, JDO2 requires the
+ * fetched Connection to be closed before continuing PersistenceManager work.
+ * For this reason, the exposed ConnectionHandle eagerly releases its JDBC
+ * Connection at the end of each JDBC data access operation (that is, on
+ * DataSourceUtils.releaseConnection).
+ * @see javax.jdo.PersistenceManager#getDataStoreConnection()
+ * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
+ */
+ public ConnectionHandle getJdbcConnection(PersistenceManager pm, boolean readOnly)
+ throws JDOException, SQLException {
+
+ return new DataStoreConnectionHandle(pm);
+ }
+
+ /**
+ * This implementation does nothing, assuming that the Connection
+ * will implicitly be closed with the PersistenceManager.
+ *
If the JDO provider returns a Connection handle that it
+ * expects the application to close, the dialect needs to invoke
+ * Connection.close here.
+ * @see java.sql.Connection#close()
+ */
+ public void releaseJdbcConnection(ConnectionHandle conHandle, PersistenceManager pm)
+ throws JDOException, SQLException {
+ }
+
+ /**
+ * This implementation delegates to JDO 2.0's flush method.
+ *
To be overridden for pre-JDO2 implementations, using the corresponding + * vendor-specific mechanism there. + * @see javax.jdo.PersistenceManager#flush() + */ + public void flush(PersistenceManager pm) throws JDOException { + pm.flush(); + } + + /** + * This implementation logs a warning that it cannot apply a query timeout. + */ + public void applyQueryTimeout(Query query, int remainingTimeInSeconds) throws JDOException { + logger.info("DefaultJdoDialect does not support query timeouts: ignoring remaining transaction time"); + } + + + //----------------------------------------------------------------------------------- + // Hook for exception translation (used by JdoTransactionManager and JdoTemplate) + //----------------------------------------------------------------------------------- + + /** + * Implementation of the PersistenceExceptionTranslator interface, + * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor. + *
Converts the exception if it is a JDOException, using this JdoDialect.
+ * Else returns null to indicate an unknown exception.
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ * @see #translateException
+ */
+ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+ if (ex instanceof JDOException) {
+ return translateException((JDOException) ex);
+ }
+ return null;
+ }
+
+ /**
+ * This implementation delegates to PersistenceManagerFactoryUtils.
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ */
+ public DataAccessException translateException(JDOException ex) {
+ if (getJdbcExceptionTranslator() != null && ex.getCause() instanceof SQLException) {
+ return getJdbcExceptionTranslator().translate("JDO operation: " + ex.getMessage(),
+ extractSqlStringFromException(ex), (SQLException) ex.getCause());
+ }
+ return PersistenceManagerFactoryUtils.convertJdoAccessException(ex);
+ }
+
+ /**
+ * Template method for extracting a SQL String from the given exception.
+ *
Default implementation always returns null. Can be overridden in
+ * subclasses to extract SQL Strings for vendor-specific exception classes.
+ * @param ex the JDOException, containing a SQLException
+ * @return the SQL String, or null if none found
+ */
+ protected String extractSqlStringFromException(JDOException ex) {
+ return null;
+ }
+
+
+ /**
+ * ConnectionHandle implementation that fetches a new JDO2 DataStoreConnection
+ * for every getConnection call and closes the Connection on
+ * releaseConnection. This is necessary because JDO2 requires the
+ * fetched Connection to be closed before continuing PersistenceManager work.
+ * @see javax.jdo.PersistenceManager#getDataStoreConnection()
+ */
+ private static class DataStoreConnectionHandle implements ConnectionHandle {
+
+ private final PersistenceManager persistenceManager;
+
+ public DataStoreConnectionHandle(PersistenceManager persistenceManager) {
+ this.persistenceManager = persistenceManager;
+ }
+
+ public Connection getConnection() {
+ return (Connection) this.persistenceManager.getDataStoreConnection();
+ }
+
+ public void releaseConnection(Connection con) {
+ JdbcUtils.closeConnection(con);
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoAccessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoAccessor.java
new file mode 100644
index 00000000000..cd7b17a583a
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoAccessor.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2006 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.jdo;
+
+import javax.jdo.JDOException;
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Base class for JdoTemplate and JdoInterceptor, defining common
+ * properties such as PersistenceManagerFactory and flushing behavior.
+ *
+ *
Note: With JDO, modifications to persistent objects are just possible + * within a transaction (in contrast to Hibernate). Therefore, eager flushing + * will just get applied when in a transaction. Furthermore, there is no + * explicit notion of flushing never, as this would not imply a performance + * gain due to JDO's field interception mechanism (which doesn't involve + * the overhead of snapshot comparisons). + * + *
Eager flushing is just available for specific JDO providers. + * You need to a corresponding JdoDialect to make eager flushing work. + * + *
Not intended to be used directly. See JdoTemplate and JdoInterceptor. + * + * @author Juergen Hoeller + * @since 02.11.2003 + * @see JdoTemplate + * @see JdoInterceptor + * @see #setFlushEager + */ +public abstract class JdoAccessor implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private PersistenceManagerFactory persistenceManagerFactory; + + private JdoDialect jdoDialect; + + private boolean flushEager = false; + + + /** + * Set the JDO PersistenceManagerFactory that should be used to create + * PersistenceManagers. + */ + public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { + this.persistenceManagerFactory = pmf; + } + + /** + * Return the JDO PersistenceManagerFactory that should be used to create + * PersistenceManagers. + */ + public PersistenceManagerFactory getPersistenceManagerFactory() { + return persistenceManagerFactory; + } + + /** + * Set the JDO dialect to use for this accessor. + *
The dialect object can be used to retrieve the underlying JDBC + * connection and to eagerly flush changes to the database. + *
Default is a DefaultJdoDialect based on the PersistenceManagerFactory's + * underlying DataSource, if any. + */ + public void setJdoDialect(JdoDialect jdoDialect) { + this.jdoDialect = jdoDialect; + } + + /** + * Return the JDO dialect to use for this accessor. + *
Creates a default one for the specified PersistenceManagerFactory if none set. + */ + public JdoDialect getJdoDialect() { + if (this.jdoDialect == null) { + this.jdoDialect = new DefaultJdoDialect(); + } + return this.jdoDialect; + } + + /** + * Set if this accessor should flush changes to the database eagerly. + *
Eager flushing leads to immediate synchronization with the database, + * even if in a transaction. This causes inconsistencies to show up and throw + * a respective exception immediately, and JDBC access code that participates + * in the same transaction will see the changes as the database is already + * aware of them then. But the drawbacks are: + *
org.springframework.dao hierarchy.
+ * Default implementation delegates to the JdoDialect. + * May be overridden in subclasses. + * @param ex JDOException that occured + * @return the corresponding DataAccessException instance + * @see JdoDialect#translateException + */ + public DataAccessException convertJdoAccessException(JDOException ex) { + return getJdoDialect().translateException(ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoCallback.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoCallback.java new file mode 100644 index 00000000000..ebcb2e72012 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoCallback.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2006 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.jdo; + +import javax.jdo.JDOException; +import javax.jdo.PersistenceManager; + +/** + * Callback interface for JDO code. To be used with {@link JdoTemplate}'s + * execution methods, often as anonymous classes within a method implementation. + * A typical implementation will call PersistenceManager CRUD to perform + * some operations on persistent objects. + * + *
Note that JDO works on bytecode-modified Java objects, to be able to
+ * perform dirty detection on each modification of a persistent instance field.
+ * In contrast to Hibernate, using returned objects outside of an active
+ * PersistenceManager poses a problem: To be able to read and modify fields
+ * e.g. in a web GUI, one has to explicitly make the instances "transient".
+ * Reassociation with a new PersistenceManager, e.g. for updates when coming
+ * back from the GUI, isn't possible, as the JDO instances have lost their
+ * identity when turned transient. This means that either value objects have
+ * to be used as parameters, or the contents of the outside-modified instance
+ * have to be copied to a freshly loaded active instance on reassociation.
+ *
+ * @author Juergen Hoeller
+ * @since 03.06.2003
+ * @see JdoTemplate
+ * @see JdoTransactionManager
+ */
+public interface JdoCallback {
+
+ /**
+ * Gets called by JdoTemplate.execute with an active JDO
+ * PersistenceManager. Does not need to care about activating
+ * or closing the PersistenceManager, or handling transactions.
+ *
+ *
Note that JDO callback code will not flush any modifications to the + * database if not executed within a transaction. Thus, you need to make + * sure that JdoTransactionManager has initiated a JDO transaction when + * the callback gets called, at least if you want to write to the database. + * + *
Allows for returning a result object created within the callback,
+ * i.e. a domain object or a collection of domain objects.
+ * A thrown custom RuntimeException is treated as an application exception:
+ * It gets propagated to the caller of the template.
+ *
+ * @param pm active PersistenceManager
+ * @return a result object, or null if none
+ * @throws JDOException if thrown by the JDO API
+ * @see JdoTemplate#execute
+ * @see JdoTemplate#executeFind
+ */
+ Object doInJdo(PersistenceManager pm) throws JDOException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoDialect.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoDialect.java
new file mode 100644
index 00000000000..0246694be88
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoDialect.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2002-2007 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.jdo;
+
+import java.sql.SQLException;
+
+import javax.jdo.JDOException;
+import javax.jdo.PersistenceManager;
+import javax.jdo.Query;
+import javax.jdo.Transaction;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.datasource.ConnectionHandle;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+
+/**
+ * SPI strategy that allows for customizing integration with a specific JDO provider,
+ * in particular regarding transaction management and exception translation. To be
+ * implemented for specific JDO providers such as JPOX, Kodo, Lido, Versant Open Access.
+ *
+ *
JDO 2.0 defines standard ways for most of the functionality covered here. + * Hence, Spring's {@link DefaultJdoDialect} uses the corresponding JDO 2.0 methods + * by default, to be overridden in a vendor-specific fashion if necessary. + * Vendor-specific subclasses of {@link DefaultJdoDialect} are still required for special + * transaction semantics and more sophisticated exception translation (if needed). + * + *
In general, it is recommended to derive from {@link DefaultJdoDialect} instead + * of implementing this interface directly. This allows for inheriting common + * behavior (present and future) from {@link DefaultJdoDialect}, only overriding + * specific hooks to plug in concrete vendor-specific behavior. + * + * @author Juergen Hoeller + * @since 02.11.2003 + * @see JdoTransactionManager#setJdoDialect + * @see JdoAccessor#setJdoDialect + * @see DefaultJdoDialect + */ +public interface JdoDialect { + + //------------------------------------------------------------------------- + // Hooks for transaction management (used by JdoTransactionManager) + //------------------------------------------------------------------------- + + /** + * Begin the given JDO transaction, applying the semantics specified by the + * given Spring transaction definition (in particular, an isolation level + * and a timeout). Invoked by JdoTransactionManager on transaction begin. + *
An implementation can configure the JDO Transaction object and then
+ * invoke begin, or invoke a special begin method that takes,
+ * for example, an isolation level.
+ *
An implementation can also apply read-only flag and isolation level to the
+ * underlying JDBC Connection before beginning the transaction. In that case,
+ * a transaction data object can be returned that holds the previous isolation
+ * level (and possibly other data), to be reset in cleanupTransaction.
+ *
Implementations can also use the Spring transaction name, as exposed by the + * passed-in TransactionDefinition, to optimize for specific data access use cases + * (effectively using the current transaction name as use case identifier). + * @param transaction the JDO transaction to begin + * @param definition the Spring transaction definition that defines semantics + * @return an arbitrary object that holds transaction data, if any + * (to be passed into cleanupTransaction) + * @throws JDOException if thrown by JDO methods + * @throws SQLException if thrown by JDBC methods + * @throws TransactionException in case of invalid arguments + * @see #cleanupTransaction + * @see javax.jdo.Transaction#begin + * @see org.springframework.jdbc.datasource.DataSourceUtils#prepareConnectionForTransaction + */ + Object beginTransaction(Transaction transaction, TransactionDefinition definition) + throws JDOException, SQLException, TransactionException; + + /** + * Clean up the transaction via the given transaction data. + * Invoked by JdoTransactionManager on transaction cleanup. + *
An implementation can, for example, reset read-only flag and
+ * isolation level of the underlying JDBC Connection. Furthermore,
+ * an exposed data access use case can be reset here.
+ * @param transactionData arbitrary object that holds transaction data, if any
+ * (as returned by beginTransaction)
+ * @see #beginTransaction
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#resetConnectionAfterTransaction
+ */
+ void cleanupTransaction(Object transactionData);
+
+ /**
+ * Retrieve the JDBC Connection that the given JDO PersistenceManager uses underneath,
+ * if accessing a relational database. This method will just get invoked if actually
+ * needing access to the underlying JDBC Connection, usually within an active JDO
+ * transaction (for example, by JdoTransactionManager). The returned handle will
+ * be passed into the releaseJdbcConnection method when not needed anymore.
+ *
Implementations are encouraged to return an unwrapped Connection object, i.e. + * the Connection as they got it from the connection pool. This makes it easier for + * application code to get at the underlying native JDBC Connection, like an + * OracleConnection, which is sometimes necessary for LOB handling etc. We assume + * that calling code knows how to properly handle the returned Connection object. + *
In a simple case where the returned Connection will be auto-closed with the
+ * PersistenceManager or can be released via the Connection object itself, an
+ * implementation can return a SimpleConnectionHandle that just contains the
+ * Connection. If some other object is needed in releaseJdbcConnection,
+ * an implementation should use a special handle that references that other object.
+ * @param pm the current JDO PersistenceManager
+ * @param readOnly whether the Connection is only needed for read-only purposes
+ * @return a handle for the JDBC Connection, to be passed into
+ * releaseJdbcConnection, or null
+ * if no JDBC Connection can be retrieved
+ * @throws JDOException if thrown by JDO methods
+ * @throws SQLException if thrown by JDBC methods
+ * @see #releaseJdbcConnection
+ * @see org.springframework.jdbc.datasource.ConnectionHandle#getConnection
+ * @see org.springframework.jdbc.datasource.SimpleConnectionHandle
+ * @see JdoTransactionManager#setDataSource
+ * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
+ */
+ ConnectionHandle getJdbcConnection(PersistenceManager pm, boolean readOnly)
+ throws JDOException, SQLException;
+
+ /**
+ * Release the given JDBC Connection, which has originally been retrieved
+ * via getJdbcConnection. This should be invoked in any case,
+ * to allow for proper release of the retrieved Connection handle.
+ *
An implementation might simply do nothing, if the Connection returned
+ * by getJdbcConnection will be implicitly closed when the JDO
+ * transaction completes or when the PersistenceManager is closed.
+ * @param conHandle the JDBC Connection handle to release
+ * @param pm the current JDO PersistenceManager
+ * @throws JDOException if thrown by JDO methods
+ * @throws SQLException if thrown by JDBC methods
+ * @see #getJdbcConnection
+ */
+ void releaseJdbcConnection(ConnectionHandle conHandle, PersistenceManager pm)
+ throws JDOException, SQLException;
+
+ /**
+ * Flush the given PersistenceManager, i.e. flush all changes (that have been
+ * applied to persistent objects) to the underlying database. This method will
+ * just get invoked when eager flushing is actually necessary, for example when
+ * JDBC access code needs to see changes within the same transaction.
+ * @param pm the current JDO PersistenceManager
+ * @throws JDOException in case of errors
+ * @see JdoAccessor#setFlushEager
+ */
+ void flush(PersistenceManager pm) throws JDOException;
+
+ /**
+ * Apply the given timeout to the given JDO query object.
+ *
Invoked with the remaining time of a specified transaction timeout, if any. + * @param query the JDO query object to apply the timeout to + * @param timeout the timeout value to apply + * @throws JDOException if thrown by JDO methods + * @see JdoTemplate#prepareQuery + */ + void applyQueryTimeout(Query query, int timeout) throws JDOException; + + + //----------------------------------------------------------------------------------- + // Hook for exception translation (used by JdoTransactionManager and JdoTemplate) + //----------------------------------------------------------------------------------- + + /** + * Translate the given JDOException to a corresponding exception from Spring's + * generic DataAccessException hierarchy. An implementation should apply + * PersistenceManagerFactoryUtils' standard exception translation if can't do + * anything more specific. + *
Of particular importance is the correct translation to + * DataIntegrityViolationException, for example on constraint violation. + * Unfortunately, standard JDO does not allow for portable detection of this. + *
Can use a SQLExceptionTranslator for translating underlying SQLExceptions
+ * in a database-specific fashion.
+ * @param ex the JDOException thrown
+ * @return the corresponding DataAccessException (must not be null)
+ * @see JdoAccessor#convertJdoAccessException
+ * @see JdoTransactionManager#convertJdoAccessException
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ * @see org.springframework.dao.DataIntegrityViolationException
+ * @see org.springframework.jdbc.support.SQLExceptionTranslator
+ */
+ DataAccessException translateException(JDOException ex);
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoInterceptor.java
new file mode 100644
index 00000000000..8bc86f5fc43
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoInterceptor.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2002-2006 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.jdo;
+
+import javax.jdo.JDOException;
+import javax.jdo.PersistenceManager;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * This interceptor binds a new JDO PersistenceManager to the thread before a method
+ * call, closing and removing it afterwards in case of any method outcome.
+ * If there already is a pre-bound PersistenceManager (e.g. from JdoTransactionManager,
+ * or from a surrounding JDO-intercepted method), the interceptor simply participates in it.
+ *
+ *
Application code must retrieve a JDO PersistenceManager via the
+ * PersistenceManagerFactoryUtils.getPersistenceManager method,
+ * to be able to detect a thread-bound PersistenceManager. It is preferable to use
+ * getPersistenceManager with allowCreate=false, if the code relies on
+ * the interceptor to provide proper PersistenceManager handling. Typically, the code
+ * will look like as follows:
+ *
+ *
+ * public void doSomeDataAccessAction() {
+ * PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(this.pmf, false);
+ * ...
+ * }
+ *
+ * Note that this interceptor automatically translates JDOExceptions, via
+ * delegating to the PersistenceManagerFactoryUtils.convertJdoAccessException
+ * method that converts them to exceptions that are compatible with the
+ * org.springframework.dao exception hierarchy (like JdoTemplate does).
+ * This can be turned off if the raw exceptions are preferred.
+ *
+ *
This class can be considered a declarative alternative to JdoTemplate's + * callback approach. The advantages are: + *
The drawback is the dependency on interceptor configuration. However, note
+ * that this interceptor is usually not necessary in scenarios where the
+ * data access code always executes within transactions. A transaction will always
+ * have a thread-bound PersistenceManager in the first place, so adding this interceptor
+ * to the configuration just adds value when fine-tuning PersistenceManager settings
+ * like the flush mode - or when relying on exception translation.
+ *
+ * @author Juergen Hoeller
+ * @since 13.06.2003
+ * @see PersistenceManagerFactoryUtils#getPersistenceManager
+ * @see JdoTransactionManager
+ * @see JdoTemplate
+ */
+public class JdoInterceptor extends JdoAccessor implements MethodInterceptor {
+
+ private boolean exceptionConversionEnabled = true;
+
+
+ /**
+ * Set whether to convert any JDOException raised to a Spring DataAccessException,
+ * compatible with the org.springframework.dao exception hierarchy.
+ *
Default is "true". Turn this flag off to let the caller receive raw exceptions + * as-is, without any wrapping. + * @see org.springframework.dao.DataAccessException + */ + public void setExceptionConversionEnabled(boolean exceptionConversionEnabled) { + this.exceptionConversionEnabled = exceptionConversionEnabled; + } + + + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + boolean existingTransaction = false; + PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(getPersistenceManagerFactory(), true); + if (TransactionSynchronizationManager.hasResource(getPersistenceManagerFactory())) { + logger.debug("Found thread-bound PersistenceManager for JDO interceptor"); + existingTransaction = true; + } + else { + logger.debug("Using new PersistenceManager for JDO interceptor"); + TransactionSynchronizationManager.bindResource(getPersistenceManagerFactory(), new PersistenceManagerHolder(pm)); + } + try { + Object retVal = methodInvocation.proceed(); + flushIfNecessary(pm, existingTransaction); + return retVal; + } + catch (JDOException ex) { + if (this.exceptionConversionEnabled) { + throw convertJdoAccessException(ex); + } + else { + throw ex; + } + } + finally { + if (existingTransaction) { + logger.debug("Not closing pre-bound JDO PersistenceManager after interceptor"); + } + else { + TransactionSynchronizationManager.unbindResource(getPersistenceManagerFactory()); + PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory()); + } + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoObjectRetrievalFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoObjectRetrievalFailureException.java new file mode 100644 index 00000000000..76b040d7e2c --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoObjectRetrievalFailureException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2006 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.jdo; + +import javax.jdo.JDOHelper; +import javax.jdo.JDOObjectNotFoundException; + +import org.springframework.orm.ObjectRetrievalFailureException; + +/** + * JDO-specific subclass of ObjectRetrievalFailureException. + * Converts JDO's JDOObjectNotFoundException. + * + * @author Juergen Hoeller + * @since 1.1 + * @see PersistenceManagerFactoryUtils#convertJdoAccessException + */ +public class JdoObjectRetrievalFailureException extends ObjectRetrievalFailureException { + + public JdoObjectRetrievalFailureException(JDOObjectNotFoundException ex) { + // Extract information about the failed object from the JDOException, if available. + super((ex.getFailedObject() != null ? ex.getFailedObject().getClass() : null), + (ex.getFailedObject() != null ? JDOHelper.getObjectId(ex.getFailedObject()) : null), + ex.getMessage(), ex); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoOperations.java new file mode 100644 index 00000000000..7704bda1e88 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoOperations.java @@ -0,0 +1,442 @@ +/* + * Copyright 2002-2007 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.jdo; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.dao.DataAccessException; + +/** + * Interface that specifies a basic set of JDO operations, + * implemented by {@link JdoTemplate}. Not often used, but a useful + * option to enhance testability, as it can easily be mocked or stubbed. + * + *
Defines JdoTemplate's data access methods that mirror
+ * various JDO {@link javax.jdo.PersistenceManager} methods. Users are
+ * strongly encouraged to read the JDO PersistenceManager
+ * javadocs for details on the semantics of those methods.
+ *
+ *
Note that lazy loading will just work with an open JDO
+ * PersistenceManager, either within a managed transaction or within
+ * {@link org.springframework.orm.jdo.support.OpenPersistenceManagerInViewFilter}/
+ * {@link org.springframework.orm.jdo.support.OpenPersistenceManagerInViewInterceptor}.
+ * Furthermore, some operations just make sense within transactions,
+ * for example: evict, evictAll, flush.
+ *
+ *
Updated to build on JDO 2.0 or higher, as of Spring 2.5. + * + * @author Juergen Hoeller + * @since 1.1 + * @see JdoTemplate + * @see javax.jdo.PersistenceManager + * @see JdoTransactionManager + * @see JdoDialect + * @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewFilter + * @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewInterceptor + */ +public interface JdoOperations { + + /** + * Execute the action specified by the given action object within a + * PersistenceManager. Application exceptions thrown by the action object + * get propagated to the caller (can only be unchecked). JDO exceptions + * are transformed into appropriate DAO ones. Allows for returning a + * result object, i.e. a domain object or a collection of domain objects. + *
Note: Callback code is not supposed to handle transactions itself!
+ * Use an appropriate transaction manager like JdoTransactionManager.
+ * @param action callback object that specifies the JDO action
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see JdoTransactionManager
+ * @see org.springframework.dao
+ * @see org.springframework.transaction
+ * @see javax.jdo.PersistenceManager
+ */
+ Object execute(JdoCallback action) throws DataAccessException;
+
+ /**
+ * Execute the specified action assuming that the result object is a
+ * Collection. This is a convenience method for executing JDO queries
+ * within an action.
+ * @param action callback object that specifies the JDO action
+ * @return a Collection result returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ */
+ Collection executeFind(JdoCallback action) throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for load, save, delete
+ //-------------------------------------------------------------------------
+
+ /**
+ * Return the persistent instance with the given JDO object id,
+ * throwing an exception if not found.
+ *
A JDO object id identifies both the persistent class and the id + * within the namespace of that class. + * @param objectId a JDO object id of the persistent instance + * @return the persistent instance + * @throws org.springframework.orm.ObjectRetrievalFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#getObjectById(Object, boolean) + */ + Object getObjectById(Object objectId) throws DataAccessException; + + /** + * Return the persistent instance of the given entity class + * with the given id value, throwing an exception if not found. + *
The given id value is typically just unique within the namespace + * of the persistent class. Its toString value must correspond to the + * toString value of the corresponding JDO object id. + *
Usually, the passed-in value will have originated from the primary + * key field of a persistent object that uses JDO's application identity. + * @param entityClass a persistent class + * @param idValue an id value of the persistent instance + * @return the persistent instance + * @throws org.springframework.orm.ObjectRetrievalFailureException if not found + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#getObjectById(Object, boolean) + * @see javax.jdo.PersistenceManager#getObjectById(Class, Object) + */ + Object getObjectById(Class entityClass, Object idValue) throws DataAccessException; + + /** + * Remove the given object from the PersistenceManager cache. + * @param entity the persistent instance to evict + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#evict(Object) + */ + void evict(Object entity) throws DataAccessException; + + /** + * Remove all given objects from the PersistenceManager cache. + * @param entities the persistent instances to evict + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#evictAll(java.util.Collection) + */ + void evictAll(Collection entities) throws DataAccessException; + + /** + * Remove all objects from the PersistenceManager cache. + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#evictAll() + */ + void evictAll() throws DataAccessException; + + /** + * Re-read the state of the given persistent instance. + * @param entity the persistent instance to re-read + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#refresh(Object) + */ + void refresh(Object entity) throws DataAccessException; + + /** + * Re-read the state of all given persistent instances. + * @param entities the persistent instances to re-read + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#refreshAll(java.util.Collection) + */ + void refreshAll(Collection entities) throws DataAccessException; + + /** + * Re-read the state of all persistent instances. + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#refreshAll() + */ + void refreshAll() throws DataAccessException; + + /** + * Make the given transient instance persistent. + * Attach the given entity if the instance is detached. + * @param entity the transient instance to make persistent + * @return the persistent instance + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#makePersistent(Object) + */ + Object makePersistent(Object entity) throws DataAccessException; + + /** + * Make the given transient instances persistent. + * Attach the given entities if the instances are detached. + * @param entities the transient instances to make persistent + * @return the persistent instances + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#makePersistentAll(java.util.Collection) + */ + Collection makePersistentAll(Collection entities) throws DataAccessException; + + /** + * Delete the given persistent instance. + * @param entity the persistent instance to delete + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#deletePersistent(Object) + */ + void deletePersistent(Object entity) throws DataAccessException; + + /** + * Delete all given persistent instances. + *
This can be combined with any of the find methods to delete by query + * in two lines of code. + * @param entities the persistent instances to delete + * @throws org.springframework.dao.DataAccessException in case of JDO errors + * @see javax.jdo.PersistenceManager#deletePersistentAll(java.util.Collection) + */ + void deletePersistentAll(Collection entities) throws DataAccessException; + + /** + * Detach a copy of the given persistent instance from the current JDO transaction, + * for use outside a JDO transaction (for example, as web form object). + * @param entity the persistent instance to detach + * @return the corresponding detached instance + * @see javax.jdo.PersistenceManager#detachCopy(Object) + */ + Object detachCopy(Object entity); + + /** + * Detach copies of the given persistent instances from the current JDO transaction, + * for use outside a JDO transaction (for example, as web form objects). + * @param entities the persistent instances to detach + * @return the corresponding detached instances + * @see javax.jdo.PersistenceManager#detachCopyAll(Collection) + */ + Collection detachCopyAll(Collection entities); + + /** + * Reattach the given detached instance (for example, a web form object) with + * the current JDO transaction, merging its changes into the current persistence + * instance that represents the corresponding entity. + *
Note that as of JDO 2.0 final, this operation is equivalent to a
+ * makePersistent call, with the latter method returning the
+ * persistence instance.
+ * @param detachedEntity the detached instance to attach
+ * @return the corresponding persistent instance
+ * @deprecated in favor of {@link #makePersistent(Object)}.
+ * To be removed in Spring 3.0.
+ */
+ Object attachCopy(Object detachedEntity);
+
+ /**
+ * Reattach the given detached instances (for example, web form objects) with
+ * the current JDO transaction, merging their changes into the current persistence
+ * instances that represent the corresponding entities.
+ *
Note that as of JDO 2.0 final, this operation is equivalent to a
+ * makePersistentAll call, with the latter method returning the
+ * persistence instance.
+ * @param detachedEntities the detached instances to reattach
+ * @return the corresponding persistent instances
+ * @deprecated in favor of {@link #makePersistentAll(java.util.Collection)}.
+ * To be removed in Spring 3.0.
+ */
+ Collection attachCopyAll(Collection detachedEntities);
+
+ /**
+ * Flush all transactional modifications to the database.
+ *
Only invoke this for selective eager flushing, for example when JDBC code
+ * needs to see certain changes within the same transaction. Else, it's preferable
+ * to rely on auto-flushing at transaction completion.
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#flush()
+ * @see JdoDialect#flush(javax.jdo.PersistenceManager)
+ */
+ void flush() throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Find all persistent instances of the given class.
+ * @param entityClass a persistent class
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class)
+ */
+ Collection find(Class entityClass) throws DataAccessException;
+
+ /**
+ * Find all persistent instances of the given class that match the given
+ * JDOQL filter.
+ * @param entityClass a persistent class
+ * @param filter the JDOQL filter to match (or null if none)
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class, String)
+ */
+ Collection find(Class entityClass, String filter) throws DataAccessException;
+
+ /**
+ * Find all persistent instances of the given class that match the given
+ * JDOQL filter, with the given result ordering.
+ * @param entityClass a persistent class
+ * @param filter the JDOQL filter to match (or null if none)
+ * @param ordering the ordering of the result (or null if none)
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class, String)
+ * @see javax.jdo.Query#setOrdering
+ */
+ Collection find(Class entityClass, String filter, String ordering) throws DataAccessException;
+
+ /**
+ * Find all persistent instances of the given class that match the given
+ * JDOQL filter, using the given parameter declarations and parameter values.
+ * @param entityClass a persistent class
+ * @param filter the JDOQL filter to match
+ * @param parameters the JDOQL parameter declarations
+ * @param values the corresponding parameter values
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class, String)
+ * @see javax.jdo.Query#declareParameters
+ * @see javax.jdo.Query#executeWithArray
+ */
+ Collection find(Class entityClass, String filter, String parameters, Object[] values)
+ throws DataAccessException;
+
+ /**
+ * Find all persistent instances of the given class that match the given
+ * JDOQL filter, using the given parameter declarations and parameter values,
+ * with the given result ordering.
+ * @param entityClass a persistent class
+ * @param filter the JDOQL filter to match
+ * @param parameters the JDOQL parameter declarations
+ * @param values the corresponding parameter values
+ * @param ordering the ordering of the result (or null if none)
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class, String)
+ * @see javax.jdo.Query#declareParameters
+ * @see javax.jdo.Query#executeWithArray
+ * @see javax.jdo.Query#setOrdering
+ */
+ Collection find(Class entityClass, String filter, String parameters, Object[] values, String ordering)
+ throws DataAccessException;
+
+ /**
+ * Find all persistent instances of the given class that match the given
+ * JDOQL filter, using the given parameter declarations and parameter values.
+ * @param entityClass a persistent class
+ * @param filter the JDOQL filter to match
+ * @param parameters the JDOQL parameter declarations
+ * @param values a Map with parameter names as keys and parameter values
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class, String)
+ * @see javax.jdo.Query#declareParameters
+ * @see javax.jdo.Query#executeWithMap
+ */
+ Collection find(Class entityClass, String filter, String parameters, Map values)
+ throws DataAccessException;
+
+ /**
+ * Find all persistent instances of the given class that match the given
+ * JDOQL filter, using the given parameter declarations and parameter values,
+ * with the given result ordering.
+ * @param entityClass a persistent class
+ * @param filter the JDOQL filter to match
+ * @param parameters the JDOQL parameter declarations
+ * @param values a Map with parameter names as keys and parameter values
+ * @param ordering the ordering of the result (or null if none)
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(Class, String)
+ * @see javax.jdo.Query#declareParameters
+ * @see javax.jdo.Query#executeWithMap
+ * @see javax.jdo.Query#setOrdering
+ */
+ Collection find(Class entityClass, String filter, String parameters, Map values, String ordering)
+ throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given query object
+ * in the specified query language.
+ * @param language the query language (javax.jdo.Query#JDOQL
+ * or javax.jdo.Query#SQL, for example)
+ * @param queryObject the query object for the specified language
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(String, Object)
+ * @see javax.jdo.Query#JDOQL
+ * @see javax.jdo.Query#SQL
+ */
+ Collection find(String language, Object queryObject) throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given single-string JDOQL query.
+ * @param queryString the single-string JDOQL query
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(String)
+ */
+ Collection find(String queryString) throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given single-string JDOQL query.
+ * @param queryString the single-string JDOQL query
+ * @param values the corresponding parameter values
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(String)
+ */
+ Collection find(String queryString, Object[] values) throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given single-string JDOQL query.
+ * @param queryString the single-string JDOQL query
+ * @param values a Map with parameter names as keys and parameter values
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newQuery(String)
+ */
+ Collection find(String queryString, Map values) throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given named query.
+ * @param entityClass a persistent class
+ * @param queryName the name of the query
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newNamedQuery(Class, String)
+ */
+ Collection findByNamedQuery(Class entityClass, String queryName) throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given named query.
+ * @param entityClass a persistent class
+ * @param queryName the name of the query
+ * @param values the corresponding parameter values
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newNamedQuery(Class, String)
+ */
+ Collection findByNamedQuery(Class entityClass, String queryName, Object[] values) throws DataAccessException;
+
+ /**
+ * Find persistent instances through the given named query.
+ * @param entityClass a persistent class
+ * @param queryName the name of the query
+ * @param values a Map with parameter names as keys and parameter values
+ * @return the persistent instances
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ * @see javax.jdo.PersistenceManager#newNamedQuery(Class, String)
+ */
+ Collection findByNamedQuery(Class entityClass, String queryName, Map values) throws DataAccessException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoOptimisticLockingFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoOptimisticLockingFailureException.java
new file mode 100644
index 00000000000..721ccd22567
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoOptimisticLockingFailureException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2006 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.jdo;
+
+import javax.jdo.JDOHelper;
+import javax.jdo.JDOOptimisticVerificationException;
+
+import org.springframework.orm.ObjectOptimisticLockingFailureException;
+
+/**
+ * JDO-specific subclass of ObjectOptimisticLockingFailureException.
+ * Converts JDO's JDOOptimisticVerificationException.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ */
+public class JdoOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
+
+ public JdoOptimisticLockingFailureException(JDOOptimisticVerificationException ex) {
+ // Extract information about the failed object from the JDOException, if available.
+ super((ex.getFailedObject() != null ? ex.getFailedObject().getClass() : null),
+ (ex.getFailedObject() != null ? JDOHelper.getObjectId(ex.getFailedObject()) : null),
+ ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoResourceFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoResourceFailureException.java
new file mode 100644
index 00000000000..c34591573f1
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoResourceFailureException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2005 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.jdo;
+
+import javax.jdo.JDODataStoreException;
+import javax.jdo.JDOFatalDataStoreException;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+
+/**
+ * JDO-specific subclass of DataAccessResourceFailureException.
+ * Converts JDO's JDODataStoreException and JDOFatalDataStoreException.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ */
+public class JdoResourceFailureException extends DataAccessResourceFailureException {
+
+ public JdoResourceFailureException(JDODataStoreException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+ public JdoResourceFailureException(JDOFatalDataStoreException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoSystemException.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoSystemException.java
new file mode 100644
index 00000000000..088b80a0b37
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoSystemException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2005 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.jdo;
+
+import javax.jdo.JDOException;
+
+import org.springframework.dao.UncategorizedDataAccessException;
+
+/**
+ * JDO-specific subclass of UncategorizedDataAccessException,
+ * for JDO system errors that do not match any concrete
+ * org.springframework.dao exceptions.
+ *
+ * @author Juergen Hoeller
+ * @since 03.06.2003
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ */
+public class JdoSystemException extends UncategorizedDataAccessException {
+
+ public JdoSystemException(JDOException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoTemplate.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoTemplate.java
new file mode 100644
index 00000000000..7f4190c6f31
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoTemplate.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jdo;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.jdo.JDOException;
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+import javax.jdo.Query;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Helper class that simplifies JDO data access code, and converts
+ * JDOExceptions into Spring DataAccessExceptions, following the
+ * org.springframework.dao exception hierarchy.
+ *
+ *
The central method is execute, supporting JDO access code
+ * implementing the {@link JdoCallback} interface. It provides JDO PersistenceManager
+ * handling such that neither the JdoCallback implementation nor the calling
+ * code needs to explicitly care about retrieving/closing PersistenceManagers,
+ * or handling JDO lifecycle exceptions.
+ *
+ *
Typically used to implement data access or business logic services that
+ * use JDO within their implementation but are JDO-agnostic in their interface.
+ * The latter or code calling the latter only have to deal with business
+ * objects, query objects, and org.springframework.dao exceptions.
+ *
+ *
Can be used within a service implementation via direct instantiation + * with a PersistenceManagerFactory reference, or get prepared in an + * application context and given to services as bean reference. + * Note: The PersistenceManagerFactory should always be configured as bean in + * the application context, in the first case given to the service directly, + * in the second case to the prepared template. + * + *
This class can be considered as direct alternative to working with the
+ * raw JDO PersistenceManager API (through
+ * PersistenceManagerFactoryUtils.getPersistenceManager()).
+ * The major advantage is its automatic conversion to DataAccessExceptions, the
+ * major disadvantage that no checked application exceptions can get thrown from
+ * within data access code. Corresponding checks and the actual throwing of such
+ * exceptions can often be deferred to after callback execution, though.
+ *
+ *
{@link LocalPersistenceManagerFactoryBean} is the preferred way of obtaining + * a reference to a specific PersistenceManagerFactory, at least in a non-EJB + * environment. The Spring application context will manage its lifecycle, + * initializing and shutting down the factory as part of the application. + * + *
Note that lazy loading will just work with an open JDO PersistenceManager,
+ * either within a Spring-driven transaction (with JdoTransactionManager or
+ * JtaTransactionManager) or within OpenPersistenceManagerInViewFilter/Interceptor.
+ * Furthermore, some operations just make sense within transactions,
+ * for example: evict, evictAll, flush.
+ *
+ *
NOTE: This class requires JDO 2.0 or higher, as of Spring 2.5. + * + * @author Juergen Hoeller + * @since 03.06.2003 + * @see #setPersistenceManagerFactory + * @see JdoCallback + * @see javax.jdo.PersistenceManager + * @see LocalPersistenceManagerFactoryBean + * @see JdoTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewFilter + * @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewInterceptor + */ +public class JdoTemplate extends JdoAccessor implements JdoOperations { + + private boolean allowCreate = true; + + private boolean exposeNativePersistenceManager = false; + + + /** + * Create a new JdoTemplate instance. + */ + public JdoTemplate() { + } + + /** + * Create a new JdoTemplate instance. + * @param pmf PersistenceManagerFactory to create PersistenceManagers + */ + public JdoTemplate(PersistenceManagerFactory pmf) { + setPersistenceManagerFactory(pmf); + afterPropertiesSet(); + } + + /** + * Create a new JdoTemplate instance. + * @param pmf PersistenceManagerFactory to create PersistenceManagers + * @param allowCreate if a non-transactional PersistenceManager should be created + * when no transactional PersistenceManager can be found for the current thread + */ + public JdoTemplate(PersistenceManagerFactory pmf, boolean allowCreate) { + setPersistenceManagerFactory(pmf); + setAllowCreate(allowCreate); + afterPropertiesSet(); + } + + /** + * Set if a new PersistenceManager should be created when no transactional + * PersistenceManager can be found for the current thread. + *
JdoTemplate is aware of a corresponding PersistenceManager bound to the
+ * current thread, for example when using JdoTransactionManager.
+ * If allowCreate is true, a new non-transactional PersistenceManager will be
+ * created if none found, which needs to be closed at the end of the operation.
+ * If false, an IllegalStateException will get thrown in this case.
+ * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
+ */
+ public void setAllowCreate(boolean allowCreate) {
+ this.allowCreate = allowCreate;
+ }
+
+ /**
+ * Return if a new PersistenceManager should be created if no thread-bound found.
+ */
+ public boolean isAllowCreate() {
+ return this.allowCreate;
+ }
+
+ /**
+ * Set whether to expose the native JDO PersistenceManager to JdoCallback
+ * code. Default is "false": a PersistenceManager proxy will be returned,
+ * suppressing close calls and automatically applying transaction
+ * timeouts (if any).
+ *
As there is often a need to cast to a provider-specific PersistenceManager
+ * class in DAOs that use provider-specific functionality, the exposed proxy
+ * implements all interfaces implemented by the original PersistenceManager.
+ * If this is not sufficient, turn this flag to "true".
+ * @see JdoCallback
+ * @see javax.jdo.PersistenceManager
+ * @see #prepareQuery
+ */
+ public void setExposeNativePersistenceManager(boolean exposeNativePersistenceManager) {
+ this.exposeNativePersistenceManager = exposeNativePersistenceManager;
+ }
+
+ /**
+ * Return whether to expose the native JDO PersistenceManager to JdoCallback
+ * code, or rather a PersistenceManager proxy.
+ */
+ public boolean isExposeNativePersistenceManager() {
+ return this.exposeNativePersistenceManager;
+ }
+
+
+ public Object execute(JdoCallback action) throws DataAccessException {
+ return execute(action, isExposeNativePersistenceManager());
+ }
+
+ public Collection executeFind(JdoCallback action) throws DataAccessException {
+ Object result = execute(action, isExposeNativePersistenceManager());
+ if (result != null && !(result instanceof Collection)) {
+ throw new InvalidDataAccessApiUsageException(
+ "Result object returned from JdoCallback isn't a Collection: [" + result + "]");
+ }
+ return (Collection) result;
+ }
+
+ /**
+ * Execute the action specified by the given action object within a
+ * PersistenceManager.
+ * @param action callback object that specifies the JDO action
+ * @param exposeNativePersistenceManager whether to expose the native
+ * JDO persistence manager to callback code
+ * @return a result object returned by the action, or null
+ * @throws org.springframework.dao.DataAccessException in case of JDO errors
+ */
+ public Object execute(JdoCallback action, boolean exposeNativePersistenceManager) throws DataAccessException {
+ Assert.notNull(action, "Callback object must not be null");
+
+ PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(
+ getPersistenceManagerFactory(), isAllowCreate());
+ boolean existingTransaction =
+ TransactionSynchronizationManager.hasResource(getPersistenceManagerFactory());
+ try {
+ PersistenceManager pmToExpose = (exposeNativePersistenceManager ? pm : createPersistenceManagerProxy(pm));
+ Object result = action.doInJdo(pmToExpose);
+ flushIfNecessary(pm, existingTransaction);
+ return postProcessResult(result, pm, existingTransaction);
+ }
+ catch (JDOException ex) {
+ throw convertJdoAccessException(ex);
+ }
+ catch (RuntimeException ex) {
+ // callback code threw application exception
+ throw ex;
+ }
+ finally {
+ PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory());
+ }
+ }
+
+ /**
+ * Create a close-suppressing proxy for the given JDO PersistenceManager.
+ * Called by the execute method.
+ *
The proxy also prepares returned JDO Query objects.
+ * @param pm the JDO PersistenceManager to create a proxy for
+ * @return the PersistenceManager proxy, implementing all interfaces
+ * implemented by the passed-in PersistenceManager object (that is,
+ * also implementing all provider-specific extension interfaces)
+ * @see javax.jdo.PersistenceManager#close()
+ * @see #execute(JdoCallback, boolean)
+ * @see #prepareQuery
+ */
+ protected PersistenceManager createPersistenceManagerProxy(PersistenceManager pm) {
+ Class[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), getClass().getClassLoader());
+ return (PersistenceManager) Proxy.newProxyInstance(
+ pm.getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(pm));
+ }
+
+ /**
+ * Post-process the given result object, which might be a Collection.
+ * Called by the execute method.
+ *
Default implementation always returns the passed-in Object as-is. + * Subclasses might override this to automatically detach result + * collections or even single result objects. + * @param pm the current JDO PersistenceManager + * @param result the result object (might be a Collection) + * @param existingTransaction if executing within an existing transaction + * (within an existing JDO PersistenceManager that won't be closed immediately) + * @return the post-processed result object (can be simply be the passed-in object) + * @see #execute(JdoCallback, boolean) + */ + protected Object postProcessResult(Object result, PersistenceManager pm, boolean existingTransaction) { + return result; + } + + + //------------------------------------------------------------------------- + // Convenience methods for load, save, delete + //------------------------------------------------------------------------- + + public Object getObjectById(final Object objectId) throws DataAccessException { + return execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + return pm.getObjectById(objectId, true); + } + }, true); + } + + public Object getObjectById(final Class entityClass, final Object idValue) throws DataAccessException { + return execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + return pm.getObjectById(entityClass, idValue); + } + }, true); + } + + public void evict(final Object entity) throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.evict(entity); + return null; + } + }, true); + } + + public void evictAll(final Collection entities) throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.evictAll(entities); + return null; + } + }, true); + } + + public void evictAll() throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.evictAll(); + return null; + } + }, true); + } + + public void refresh(final Object entity) throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.refresh(entity); + return null; + } + }, true); + } + + public void refreshAll(final Collection entities) throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.refreshAll(entities); + return null; + } + }, true); + } + + public void refreshAll() throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.refreshAll(); + return null; + } + }, true); + } + + public Object makePersistent(final Object entity) throws DataAccessException { + return execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + return pm.makePersistent(entity); + } + }, true); + } + + public Collection makePersistentAll(final Collection entities) throws DataAccessException { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + return pm.makePersistentAll(entities); + } + }, true); + } + + public void deletePersistent(final Object entity) throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.deletePersistent(entity); + return null; + } + }, true); + } + + public void deletePersistentAll(final Collection entities) throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + pm.deletePersistentAll(entities); + return null; + } + }, true); + } + + public Object detachCopy(final Object entity) { + return execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + return pm.detachCopy(entity); + } + }, true); + } + + public Collection detachCopyAll(final Collection entities) { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + return pm.detachCopyAll(entities); + } + }, true); + } + + /** + * @deprecated in favor of {@link #makePersistent(Object)}. + * To be removed in Spring 3.0. + */ + public Object attachCopy(Object detachedEntity) { + return makePersistent(detachedEntity); + } + + /** + * @deprecated in favor of {@link #makePersistentAll(java.util.Collection)}. + * To be removed in Spring 3.0. + */ + public Collection attachCopyAll(Collection detachedEntities) { + return makePersistentAll(detachedEntities); + } + + public void flush() throws DataAccessException { + execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + getJdoDialect().flush(pm); + return null; + } + }, true); + } + + + //------------------------------------------------------------------------- + // Convenience finder methods + //------------------------------------------------------------------------- + + public Collection find(Class entityClass) throws DataAccessException { + return find(entityClass, null, null); + } + + public Collection find(Class entityClass, String filter) throws DataAccessException { + return find(entityClass, filter, null); + } + + public Collection find(final Class entityClass, final String filter, final String ordering) + throws DataAccessException { + + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = (filter != null ? pm.newQuery(entityClass, filter) : pm.newQuery(entityClass)); + prepareQuery(query); + if (ordering != null) { + query.setOrdering(ordering); + } + return query.execute(); + } + }, true); + } + + public Collection find(Class entityClass, String filter, String parameters, Object[] values) + throws DataAccessException { + + return find(entityClass, filter, parameters, values, null); + } + + public Collection find( + final Class entityClass, final String filter, final String parameters, final Object[] values, + final String ordering) throws DataAccessException { + + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newQuery(entityClass, filter); + prepareQuery(query); + query.declareParameters(parameters); + if (ordering != null) { + query.setOrdering(ordering); + } + return query.executeWithArray(values); + } + }, true); + } + + public Collection find(Class entityClass, String filter, String parameters, Map values) + throws DataAccessException { + + return find(entityClass, filter, parameters, values, null); + } + + public Collection find( + final Class entityClass, final String filter, final String parameters, final Map values, + final String ordering) throws DataAccessException { + + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newQuery(entityClass, filter); + prepareQuery(query); + query.declareParameters(parameters); + if (ordering != null) { + query.setOrdering(ordering); + } + return query.executeWithMap(values); + } + }, true); + } + + public Collection find(final String language, final Object queryObject) throws DataAccessException { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newQuery(language, queryObject); + prepareQuery(query); + return query.execute(); + } + }, true); + } + + public Collection find(final String queryString) throws DataAccessException { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newQuery(queryString); + prepareQuery(query); + return query.execute(); + } + }, true); + } + + public Collection find(final String queryString, final Object[] values) throws DataAccessException { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newQuery(queryString); + prepareQuery(query); + return query.executeWithArray(values); + } + }, true); + } + + public Collection find(final String queryString, final Map values) throws DataAccessException { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newQuery(queryString); + prepareQuery(query); + return query.executeWithMap(values); + } + }, true); + } + + public Collection findByNamedQuery(final Class entityClass, final String queryName) throws DataAccessException { + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newNamedQuery(entityClass, queryName); + prepareQuery(query); + return query.execute(); + } + }, true); + } + + public Collection findByNamedQuery(final Class entityClass, final String queryName, final Object[] values) + throws DataAccessException { + + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newNamedQuery(entityClass, queryName); + prepareQuery(query); + return query.executeWithArray(values); + } + }, true); + } + + public Collection findByNamedQuery(final Class entityClass, final String queryName, final Map values) + throws DataAccessException { + + return (Collection) execute(new JdoCallback() { + public Object doInJdo(PersistenceManager pm) throws JDOException { + Query query = pm.newNamedQuery(entityClass, queryName); + prepareQuery(query); + return query.executeWithMap(values); + } + }, true); + } + + + /** + * Prepare the given JDO query object. To be used within a JdoCallback. + * Applies a transaction timeout, if any. If you don't use such timeouts, + * the call is a no-op. + *
In general, prefer a proxied PersistenceManager instead, which will + * automatically apply the transaction timeout (through the use of a special + * PersistenceManager proxy). You need to set the "exposeNativePersistenceManager" + * property to "false" to activate this. Note that you won't be able to cast + * to a provider-specific JDO PersistenceManager class anymore then. + * @param query the JDO query object + * @throws JDOException if the query could not be properly prepared + * @see JdoCallback#doInJdo + * @see PersistenceManagerFactoryUtils#applyTransactionTimeout + * @see #setExposeNativePersistenceManager + */ + public void prepareQuery(Query query) throws JDOException { + PersistenceManagerFactoryUtils.applyTransactionTimeout( + query, getPersistenceManagerFactory(), getJdoDialect()); + } + + + /** + * Invocation handler that suppresses close calls on JDO PersistenceManagers. + * Also prepares returned Query objects. + * @see javax.jdo.PersistenceManager#close() + */ + private class CloseSuppressingInvocationHandler implements InvocationHandler { + + private final PersistenceManager target; + + public CloseSuppressingInvocationHandler(PersistenceManager target) { + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // Invocation on PersistenceManager interface (or provider-specific extension) coming in... + + if (method.getName().equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } + else if (method.getName().equals("hashCode")) { + // Use hashCode of PersistenceManager proxy. + return new Integer(System.identityHashCode(proxy)); + } + else if (method.getName().equals("close")) { + // Handle close method: suppress, not valid. + return null; + } + + // Invoke method on target PersistenceManager. + try { + Object retVal = method.invoke(this.target, args); + + // If return value is a JDO Query object, apply transaction timeout. + if (retVal instanceof Query) { + prepareQuery(((Query) retVal)); + } + + return retVal; + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java new file mode 100644 index 00000000000..b6402b2e9c8 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java @@ -0,0 +1,587 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.jdo; + +import javax.jdo.JDOException; +import javax.jdo.PersistenceManager; +import javax.jdo.PersistenceManagerFactory; +import javax.jdo.Transaction; +import javax.sql.DataSource; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.ConnectionHandle; +import org.springframework.jdbc.datasource.ConnectionHolder; +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.TransactionDefinition; +import org.springframework.transaction.TransactionException; +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 JDO {@link javax.jdo.PersistenceManagerFactory}. Binds a JDO + * PersistenceManager from the specified factory to the thread, potentially allowing + * for one thread-bound PersistenceManager per factory. + * {@link PersistenceManagerFactoryUtils} and {@link JdoTemplate} are aware of + * thread-bound persistence managers and participate in such transactions automatically. + * Using either of those (or going through a {@link TransactionAwarePersistenceManagerFactoryProxy} + * is required for JDO access code supporting this transaction management mechanism. + * + *
This transaction manager is appropriate for applications that use a single + * JDO PersistenceManagerFactory for transactional data access. JTA (usually through + * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary + * for accessing multiple transactional resources within the same transaction. + * Note that you need to configure your JDO provider accordingly in order to make + * it participate in JTA transactions. + * + *
This transaction manager 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 JDO and services which use plain + * JDBC (without being aware of JDO)! Application code needs to stick to the + * same simple Connection lookup pattern as with + * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection} + * or going through a + * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}). + * + *
Note: To be able to register a DataSource's Connection for plain JDBC code, + * this instance needs to be aware of the DataSource ({@link #setDataSource}). + * The given DataSource should obviously match the one used by the given + * PersistenceManagerFactory. This transaction manager will autodetect the DataSource + * that acts as "connectionFactory" of the PersistenceManagerFactory, so you usually + * don't need to explicitly specify the "dataSource" property. + * + *
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 JDO PersistenceManager 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 JDO transactions (provided that your + * JDBC driver supports Savepoints). Note that JDO itself does not support + * nested transactions! Hence, do not expect JDO access code to semantically + * participate in a nested transaction. + * + * @author Juergen Hoeller + * @since 03.06.2003 + * @see #setPersistenceManagerFactory + * @see #setDataSource + * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory + * @see LocalPersistenceManagerFactoryBean + * @see PersistenceManagerFactoryUtils#getPersistenceManager + * @see PersistenceManagerFactoryUtils#releasePersistenceManager + * @see JdoTemplate + * @see TransactionAwarePersistenceManagerFactoryProxy + * @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 + */ +public class JdoTransactionManager extends AbstractPlatformTransactionManager + implements ResourceTransactionManager, InitializingBean { + + private PersistenceManagerFactory persistenceManagerFactory; + + private DataSource dataSource; + + private boolean autodetectDataSource = true; + + private JdoDialect jdoDialect; + + + /** + * Create a new JdoTransactionManager instance. + * A PersistenceManagerFactory has to be set to be able to use it. + * @see #setPersistenceManagerFactory + */ + public JdoTransactionManager() { + } + + /** + * Create a new JdoTransactionManager instance. + * @param pmf PersistenceManagerFactory to manage transactions for + */ + public JdoTransactionManager(PersistenceManagerFactory pmf) { + this.persistenceManagerFactory = pmf; + afterPropertiesSet(); + } + + + /** + * Set the PersistenceManagerFactory that this instance should manage transactions for. + *
The PersistenceManagerFactory specified here should be the target + * PersistenceManagerFactory to manage transactions for, not a + * TransactionAwarePersistenceManagerFactoryProxy. Only data access + * code may work with TransactionAwarePersistenceManagerFactoryProxy, while the + * transaction manager needs to work on the underlying target PersistenceManagerFactory. + * @see TransactionAwarePersistenceManagerFactoryProxy + */ + public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { + this.persistenceManagerFactory = pmf; + } + + /** + * Return the PersistenceManagerFactory that this instance should manage transactions for. + */ + public PersistenceManagerFactory getPersistenceManagerFactory() { + return this.persistenceManagerFactory; + } + + /** + * Set the JDBC DataSource that this instance should manage transactions for. + * The DataSource should match the one used by the JDO PersistenceManagerFactory: + * for example, you could specify the same JNDI DataSource for both. + *
If the PersistenceManagerFactory uses a DataSource as connection factory, + * the DataSource will be autodetected: You can still explictly specify the + * DataSource, but you don't need to in this case. + *
A transactional JDBC Connection for this DataSource will be provided to + * application code accessing this DataSource directly via DataSourceUtils + * or JdbcTemplate. The Connection will be taken from the JDO PersistenceManager. + *
Note that you need to use a JDO dialect for a specific JDO provider to + * allow for exposing JDO transactions as JDBC transactions. + *
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 #setJdoDialect
+ * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory
+ * @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 JDO PersistenceManagerFactory,
+ * as returned by the getConnectionFactory() method. Default is "true".
+ *
Can be turned off to deliberately ignore an available DataSource, + * to not expose JDO transactions as JDBC transactions for that DataSource. + * @see #setDataSource + * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory + */ + public void setAutodetectDataSource(boolean autodetectDataSource) { + this.autodetectDataSource = autodetectDataSource; + } + + /** + * Set the JDO dialect to use for this transaction manager. + *
The dialect object can be used to retrieve the underlying JDBC connection + * and thus allows for exposing JDO transactions as JDBC transactions. + * @see JdoDialect#getJdbcConnection + */ + public void setJdoDialect(JdoDialect jdoDialect) { + this.jdoDialect = jdoDialect; + } + + /** + * Return the JDO dialect to use for this transaction manager. + *
Creates a default one for the specified PersistenceManagerFactory if none set.
+ */
+ public JdoDialect getJdoDialect() {
+ if (this.jdoDialect == null) {
+ this.jdoDialect = new DefaultJdoDialect();
+ }
+ return this.jdoDialect;
+ }
+
+ /**
+ * Eagerly initialize the JDO dialect, creating a default one
+ * for the specified PersistenceManagerFactory if none set.
+ * Auto-detect the PersistenceManagerFactory's DataSource, if any.
+ */
+ public void afterPropertiesSet() {
+ if (getPersistenceManagerFactory() == null) {
+ throw new IllegalArgumentException("Property 'persistenceManagerFactory' is required");
+ }
+ // Build default JdoDialect if none explicitly specified.
+ if (this.jdoDialect == null) {
+ this.jdoDialect = new DefaultJdoDialect(getPersistenceManagerFactory().getConnectionFactory());
+ }
+
+ // Check for DataSource as connection factory.
+ if (this.autodetectDataSource && getDataSource() == null) {
+ Object pmfcf = getPersistenceManagerFactory().getConnectionFactory();
+ if (pmfcf instanceof DataSource) {
+ // Use the PersistenceManagerFactory's DataSource for exposing transactions to JDBC code.
+ this.dataSource = (DataSource) pmfcf;
+ if (logger.isInfoEnabled()) {
+ logger.info("Using DataSource [" + this.dataSource +
+ "] of JDO PersistenceManagerFactory for JdoTransactionManager");
+ }
+ }
+ }
+ }
+
+
+ public Object getResourceFactory() {
+ return getPersistenceManagerFactory();
+ }
+
+ protected Object doGetTransaction() {
+ JdoTransactionObject txObject = new JdoTransactionObject();
+ txObject.setSavepointAllowed(isNestedTransactionAllowed());
+
+ PersistenceManagerHolder pmHolder = (PersistenceManagerHolder)
+ TransactionSynchronizationManager.getResource(getPersistenceManagerFactory());
+ if (pmHolder != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found thread-bound PersistenceManager [" +
+ pmHolder.getPersistenceManager() + "] for JDO transaction");
+ }
+ txObject.setPersistenceManagerHolder(pmHolder, false);
+ }
+
+ if (getDataSource() != null) {
+ ConnectionHolder conHolder = (ConnectionHolder)
+ TransactionSynchronizationManager.getResource(getDataSource());
+ txObject.setConnectionHolder(conHolder);
+ }
+
+ return txObject;
+ }
+
+ protected boolean isExistingTransaction(Object transaction) {
+ return ((JdoTransactionObject) transaction).hasTransaction();
+ }
+
+ protected void doBegin(Object transaction, TransactionDefinition definition) {
+ JdoTransactionObject txObject = (JdoTransactionObject) transaction;
+
+ if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
+ throw new IllegalTransactionStateException(
+ "Pre-bound JDBC Connection found! JdoTransactionManager does not support " +
+ "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
+ "It is recommended to use a single JdoTransactionManager for all transactions " +
+ "on a single DataSource, no matter whether JDO or JDBC access.");
+ }
+
+ PersistenceManager pm = null;
+
+ try {
+ if (txObject.getPersistenceManagerHolder() == null ||
+ txObject.getPersistenceManagerHolder().isSynchronizedWithTransaction()) {
+ PersistenceManager newPm = getPersistenceManagerFactory().getPersistenceManager();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Opened new PersistenceManager [" + newPm + "] for JDO transaction");
+ }
+ txObject.setPersistenceManagerHolder(new PersistenceManagerHolder(newPm), true);
+ }
+
+ pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
+
+ // Delegate to JdoDialect for actual transaction begin.
+ Object transactionData = getJdoDialect().beginTransaction(pm.currentTransaction(), definition);
+ txObject.setTransactionData(transactionData);
+
+ // Register transaction timeout.
+ int timeout = determineTimeout(definition);
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ txObject.getPersistenceManagerHolder().setTimeoutInSeconds(timeout);
+ }
+
+ // Register the JDO PersistenceManager's JDBC Connection for the DataSource, if set.
+ if (getDataSource() != null) {
+ ConnectionHandle conHandle = getJdoDialect().getJdbcConnection(pm, definition.isReadOnly());
+ if (conHandle != null) {
+ ConnectionHolder conHolder = new ConnectionHolder(conHandle);
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ conHolder.setTimeoutInSeconds(timeout);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exposing JDO transaction as JDBC transaction [" + conHolder.getConnectionHandle() + "]");
+ }
+ TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
+ txObject.setConnectionHolder(conHolder);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Not exposing JDO transaction [" + pm + "] as JDBC transaction because JdoDialect [" +
+ getJdoDialect() + "] does not support JDBC Connection retrieval");
+ }
+ }
+ }
+
+ // Bind the persistence manager holder to the thread.
+ if (txObject.isNewPersistenceManagerHolder()) {
+ TransactionSynchronizationManager.bindResource(
+ getPersistenceManagerFactory(), txObject.getPersistenceManagerHolder());
+ }
+ txObject.getPersistenceManagerHolder().setSynchronizedWithTransaction(true);
+ }
+
+ catch (TransactionException ex) {
+ closePersistenceManagerAfterFailedBegin(txObject);
+ throw ex;
+ }
+ catch (Exception ex) {
+ closePersistenceManagerAfterFailedBegin(txObject);
+ throw new CannotCreateTransactionException("Could not open JDO PersistenceManager for transaction", ex);
+ }
+ }
+
+ /**
+ * Close the current transaction's EntityManager.
+ * Called after a transaction begin attempt failed.
+ * @param txObject the current transaction
+ */
+ protected void closePersistenceManagerAfterFailedBegin(JdoTransactionObject txObject) {
+ if (txObject.isNewPersistenceManagerHolder()) {
+ PersistenceManager pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
+ try {
+ if (pm.currentTransaction().isActive()) {
+ pm.currentTransaction().rollback();
+ }
+ }
+ catch (Throwable ex) {
+ logger.debug("Could not rollback PersistenceManager after failed transaction begin", ex);
+ }
+ finally {
+ PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory());
+ }
+ }
+ }
+
+ protected Object doSuspend(Object transaction) {
+ JdoTransactionObject txObject = (JdoTransactionObject) transaction;
+ txObject.setPersistenceManagerHolder(null, false);
+ PersistenceManagerHolder persistenceManagerHolder = (PersistenceManagerHolder)
+ TransactionSynchronizationManager.unbindResource(getPersistenceManagerFactory());
+ txObject.setConnectionHolder(null);
+ ConnectionHolder connectionHolder = null;
+ if (getDataSource() != null && TransactionSynchronizationManager.hasResource(getDataSource())) {
+ connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
+ }
+ return new SuspendedResourcesHolder(persistenceManagerHolder, connectionHolder);
+ }
+
+ protected void doResume(Object transaction, Object suspendedResources) {
+ SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
+ TransactionSynchronizationManager.bindResource(
+ getPersistenceManagerFactory(), resourcesHolder.getPersistenceManagerHolder());
+ if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) {
+ TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
+ }
+ }
+
+ /**
+ * This implementation returns "true": a JDO2 commit will properly handle
+ * transactions that have been marked rollback-only at a global level.
+ */
+ protected boolean shouldCommitOnGlobalRollbackOnly() {
+ return true;
+ }
+
+ protected void doCommit(DefaultTransactionStatus status) {
+ JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Committing JDO transaction on PersistenceManager [" +
+ txObject.getPersistenceManagerHolder().getPersistenceManager() + "]");
+ }
+ try {
+ Transaction tx = txObject.getPersistenceManagerHolder().getPersistenceManager().currentTransaction();
+ tx.commit();
+ }
+ catch (JDOException ex) {
+ // Assumably failed to flush changes to database.
+ throw convertJdoAccessException(ex);
+ }
+ }
+
+ protected void doRollback(DefaultTransactionStatus status) {
+ JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Rolling back JDO transaction on PersistenceManager [" +
+ txObject.getPersistenceManagerHolder().getPersistenceManager() + "]");
+ }
+ try {
+ Transaction tx = txObject.getPersistenceManagerHolder().getPersistenceManager().currentTransaction();
+ if (tx.isActive()) {
+ tx.rollback();
+ }
+ }
+ catch (JDOException ex) {
+ throw new TransactionSystemException("Could not roll back JDO transaction", ex);
+ }
+ }
+
+ protected void doSetRollbackOnly(DefaultTransactionStatus status) {
+ JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Setting JDO transaction on PersistenceManager [" +
+ txObject.getPersistenceManagerHolder().getPersistenceManager() + "] rollback-only");
+ }
+ txObject.setRollbackOnly();
+ }
+
+ protected void doCleanupAfterCompletion(Object transaction) {
+ JdoTransactionObject txObject = (JdoTransactionObject) transaction;
+
+ // Remove the persistence manager holder from the thread.
+ if (txObject.isNewPersistenceManagerHolder()) {
+ TransactionSynchronizationManager.unbindResource(getPersistenceManagerFactory());
+ }
+ txObject.getPersistenceManagerHolder().clear();
+
+ // Remove the JDBC connection holder from the thread, if exposed.
+ if (txObject.hasConnectionHolder()) {
+ TransactionSynchronizationManager.unbindResource(getDataSource());
+ try {
+ getJdoDialect().releaseJdbcConnection(txObject.getConnectionHolder().getConnectionHandle(),
+ txObject.getPersistenceManagerHolder().getPersistenceManager());
+ }
+ catch (Throwable ex) {
+ // Just log it, to keep a transaction-related exception.
+ logger.debug("Could not release JDBC connection after transaction", ex);
+ }
+ }
+
+ getJdoDialect().cleanupTransaction(txObject.getTransactionData());
+
+ if (txObject.isNewPersistenceManagerHolder()) {
+ PersistenceManager pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Closing JDO PersistenceManager [" + pm + "] after transaction");
+ }
+ PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory());
+ }
+ else {
+ logger.debug("Not closing pre-bound JDO PersistenceManager after transaction");
+ }
+ }
+
+ /**
+ * Convert the given JDOException to an appropriate exception from the
+ * org.springframework.dao hierarchy.
+ *
Default implementation delegates to the JdoDialect.
+ * May be overridden in subclasses.
+ * @param ex JDOException that occured
+ * @return the corresponding DataAccessException instance
+ * @see JdoDialect#translateException
+ */
+ protected DataAccessException convertJdoAccessException(JDOException ex) {
+ return getJdoDialect().translateException(ex);
+ }
+
+
+ /**
+ * JDO transaction object, representing a PersistenceManagerHolder.
+ * Used as transaction object by JdoTransactionManager.
+ */
+ private static class JdoTransactionObject extends JdbcTransactionObjectSupport {
+
+ private PersistenceManagerHolder persistenceManagerHolder;
+
+ private boolean newPersistenceManagerHolder;
+
+ private Object transactionData;
+
+ public void setPersistenceManagerHolder(
+ PersistenceManagerHolder persistenceManagerHolder, boolean newPersistenceManagerHolder) {
+ this.persistenceManagerHolder = persistenceManagerHolder;
+ this.newPersistenceManagerHolder = newPersistenceManagerHolder;
+ }
+
+ public PersistenceManagerHolder getPersistenceManagerHolder() {
+ return persistenceManagerHolder;
+ }
+
+ public boolean isNewPersistenceManagerHolder() {
+ return newPersistenceManagerHolder;
+ }
+
+ public boolean hasTransaction() {
+ return (this.persistenceManagerHolder != null && this.persistenceManagerHolder.isTransactionActive());
+ }
+
+ public void setTransactionData(Object transactionData) {
+ this.transactionData = transactionData;
+ this.persistenceManagerHolder.setTransactionActive(true);
+ }
+
+ public Object getTransactionData() {
+ return transactionData;
+ }
+
+ public void setRollbackOnly() {
+ Transaction tx = this.persistenceManagerHolder.getPersistenceManager().currentTransaction();
+ if (tx.isActive()) {
+ tx.setRollbackOnly();
+ }
+ if (hasConnectionHolder()) {
+ getConnectionHolder().setRollbackOnly();
+ }
+ }
+
+ public boolean isRollbackOnly() {
+ Transaction tx = this.persistenceManagerHolder.getPersistenceManager().currentTransaction();
+ return tx.getRollbackOnly();
+ }
+ }
+
+
+ /**
+ * Holder for suspended resources.
+ * Used internally by doSuspend and doResume.
+ */
+ private static class SuspendedResourcesHolder {
+
+ private final PersistenceManagerHolder persistenceManagerHolder;
+
+ private final ConnectionHolder connectionHolder;
+
+ private SuspendedResourcesHolder(PersistenceManagerHolder pmHolder, ConnectionHolder conHolder) {
+ this.persistenceManagerHolder = pmHolder;
+ this.connectionHolder = conHolder;
+ }
+
+ private PersistenceManagerHolder getPersistenceManagerHolder() {
+ return this.persistenceManagerHolder;
+ }
+
+ private ConnectionHolder getConnectionHolder() {
+ return this.connectionHolder;
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoUsageException.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoUsageException.java
new file mode 100644
index 00000000000..b4c44419106
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/JdoUsageException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2005 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.jdo;
+
+import javax.jdo.JDOFatalUserException;
+import javax.jdo.JDOUserException;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+
+/**
+ * JDO-specific subclass of InvalidDataAccessApiUsageException.
+ * Converts JDO's JDOUserException and JDOFatalUserException.
+ *
+ * @author Juergen Hoeller
+ * @since 03.06.2003
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ */
+public class JdoUsageException extends InvalidDataAccessApiUsageException {
+
+ public JdoUsageException(JDOUserException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+ public JdoUsageException(JDOFatalUserException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/LocalPersistenceManagerFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/LocalPersistenceManagerFactoryBean.java
new file mode 100644
index 00000000000..172b3c83808
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/LocalPersistenceManagerFactoryBean.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2007 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.jdo;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.jdo.JDOException;
+import javax.jdo.JDOHelper;
+import javax.jdo.PersistenceManagerFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * {@link org.springframework.beans.factory.FactoryBean} that creates a
+ * JDO {@link javax.jdo.PersistenceManagerFactory}. This is the usual way to
+ * set up a shared JDO PersistenceManagerFactory in a Spring application context;
+ * the PersistenceManagerFactory can then be passed to JDO-based DAOs via
+ * dependency injection. Note that switching to a JNDI lookup or to a bean-style
+ * PersistenceManagerFactory instance is just a matter of configuration!
+ *
+ *
Configuration settings can either be read from a properties file, + * specified as "configLocation", or locally specified. Properties + * specified as "jdoProperties" here will override any settings in a file. + * On JDO 2.1, you may alternatively specify a "persistenceManagerFactoryName", + * referring to a PMF definition in "META-INF/jdoconfig.xml" + * (see {@link #setPersistenceManagerFactoryName}). + * + *
NOTE: This class requires JDO 2.0 or higher, as of Spring 2.5. + * + *
This class also implements the + * {@link org.springframework.dao.support.PersistenceExceptionTranslator} + * interface, as autodetected by Spring's + * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}, + * for AOP-based translation of native exceptions to Spring DataAccessExceptions. + * Hence, the presence of a LocalPersistenceManagerFactoryBean automatically enables + * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions. + * + *
Alternative: Configuration of a PersistenceManagerFactory provider bean + * + *
As alternative to the properties-driven approach that this FactoryBean offers + * (which is analogous to using the standard JDOHelper class with a Properties + * object that is populated with standard JDO properties), you can set up an + * instance of your PersistenceManagerFactory implementation class directly. + * + *
Like a DataSource, a PersistenceManagerFactory is encouraged to + * support bean-style configuration, which makes it very easy to set up as + * Spring-managed bean. The implementation class becomes the bean class; + * the remaining properties are applied as bean properties (starting with + * lower-case characters, in contrast to the corresponding JDO properties). + * + *
For example, in case of JPOX: + * + *
+ * <bean id="persistenceManagerFactory" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close"> + * <property name="connectionFactory" ref="dataSource"/> + * <property name="nontransactionalRead" value="true"/> + * </bean> + *+ * + *
Note that such direct setup of a PersistenceManagerFactory implementation + * is the only way to pass an external connection factory (i.e. a JDBC DataSource) + * into a JDO PersistenceManagerFactory. With the standard properties-driven approach, + * you can only use an internal connection pool or a JNDI DataSource. + * + *
The close() method is standardized in JDO; don't forget to
+ * specify it as "destroy-method" for any PersistenceManagerFactory instance.
+ * Note that this FactoryBean will automatically invoke close() for
+ * the PersistenceManagerFactory that it creates, without any special configuration.
+ *
+ * @author Juergen Hoeller
+ * @since 03.06.2003
+ * @see JdoTemplate#setPersistenceManagerFactory
+ * @see JdoTransactionManager#setPersistenceManagerFactory
+ * @see org.springframework.jndi.JndiObjectFactoryBean
+ * @see javax.jdo.JDOHelper#getPersistenceManagerFactory
+ * @see javax.jdo.PersistenceManagerFactory#setConnectionFactory
+ * @see javax.jdo.PersistenceManagerFactory#close()
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ */
+public class LocalPersistenceManagerFactoryBean
+ implements FactoryBean, BeanClassLoaderAware, InitializingBean, DisposableBean, PersistenceExceptionTranslator {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private String persistenceManagerFactoryName;
+
+ private Resource configLocation;
+
+ private final Map jdoPropertyMap = new HashMap();
+
+ private ClassLoader beanClassLoader;
+
+ private PersistenceManagerFactory persistenceManagerFactory;
+
+ private JdoDialect jdoDialect;
+
+
+ /**
+ * Specify the name of the desired PersistenceManagerFactory.
+ *
This may either be a properties resource in the classpath if such a resource exists + * (JDO 2.0), or a PMF definition with that name from "META-INF/jdoconfig.xml" (JDO 2.1), + * or a JPA EntityManagerFactory cast to a PersistenceManagerFactory based on the + * persistence-unit name from "META-INF/persistence.xml" (JDO 2.1 / JPA 1.0). + *
Default is none: Either 'persistenceManagerFactoryName' or 'configLocation' + * or 'jdoProperties' needs to be specified. + * @see #setConfigLocation + * @see #setJdoProperties + */ + public void setPersistenceManagerFactoryName(String persistenceManagerFactoryName) { + this.persistenceManagerFactoryName = persistenceManagerFactoryName; + } + + /** + * Set the location of the JDO properties config file, for example + * as classpath resource "classpath:kodo.properties". + *
Note: Can be omitted when all necessary properties are + * specified locally via this bean. + */ + public void setConfigLocation(Resource configLocation) { + this.configLocation = configLocation; + } + + /** + * Set JDO properties, such as"javax.jdo.PersistenceManagerFactoryClass". + *
Can be used to override values in a JDO properties config file, + * or to specify all necessary properties locally. + *
Can be populated with a String "value" (parsed via PropertiesEditor)
+ * or a "props" element in XML bean definitions.
+ */
+ public void setJdoProperties(Properties jdoProperties) {
+ CollectionUtils.mergePropertiesIntoMap(jdoProperties, this.jdoPropertyMap);
+ }
+
+ /**
+ * Specify JDO properties as a Map, to be passed into
+ * JDOHelper.getPersistenceManagerFactory (if any).
+ *
Can be populated with a "map" or "props" element in XML bean definitions. + * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(java.util.Map) + */ + public void setJdoPropertyMap(Map jdoProperties) { + if (jdoProperties != null) { + this.jdoPropertyMap.putAll(jdoProperties); + } + } + + /** + * Allow Map access to the JDO properties to be passed to the JDOHelper, + * with the option to add or override specific entries. + *
Useful for specifying entries directly, for example via + * "jdoPropertyMap[myKey]". + */ + public Map getJdoPropertyMap() { + return this.jdoPropertyMap; + } + /** + * Set the JDO dialect to use for the PersistenceExceptionTranslator + * functionality of this factory. + *
Default is a DefaultJdoDialect based on the PersistenceManagerFactory's + * underlying DataSource, if any. + * @see JdoDialect#translateException + * @see #translateExceptionIfPossible + * @see org.springframework.dao.support.PersistenceExceptionTranslator + */ + public void setJdoDialect(JdoDialect jdoDialect) { + this.jdoDialect = jdoDialect; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + /** + * Initialize the PersistenceManagerFactory for the given location. + * @throws IllegalArgumentException in case of illegal property values + * @throws IOException if the properties could not be loaded from the given location + * @throws JDOException in case of JDO initialization errors + */ + public void afterPropertiesSet() throws IllegalArgumentException, IOException, JDOException { + if (this.persistenceManagerFactoryName != null) { + if (this.configLocation != null || !this.jdoPropertyMap.isEmpty()) { + throw new IllegalStateException("'configLocation'/'jdoProperties' not supported in " + + "combination with 'persistenceManagerFactoryName' - specify one or the other, not both"); + } + if (logger.isInfoEnabled()) { + logger.info("Building new JDO PersistenceManagerFactory for name '" + + this.persistenceManagerFactoryName + "'"); + } + this.persistenceManagerFactory = newPersistenceManagerFactory(this.persistenceManagerFactoryName); + } + + else { + Map mergedProps = new HashMap(); + + if (this.configLocation != null) { + if (logger.isInfoEnabled()) { + logger.info("Loading JDO config from [" + this.configLocation + "]"); + } + mergedProps.putAll(PropertiesLoaderUtils.loadProperties(this.configLocation)); + } + + mergedProps.putAll(this.jdoPropertyMap); + + // Build PersistenceManagerFactory instance. + logger.info("Building new JDO PersistenceManagerFactory"); + this.persistenceManagerFactory = newPersistenceManagerFactory(mergedProps); + } + + // Build default JdoDialect if none explicitly specified. + if (this.jdoDialect == null) { + this.jdoDialect = new DefaultJdoDialect(this.persistenceManagerFactory.getConnectionFactory()); + } + } + + /** + * Subclasses can override this to perform custom initialization of the + * PersistenceManagerFactory instance, creating it for the specified name. + *
The default implementation invokes JDOHelper's
+ * getPersistenceManagerFactory(String) method.
+ * A custom implementation could prepare the instance in a specific way,
+ * or use a custom PersistenceManagerFactory implementation.
+ * @param name the name of the desired PersistenceManagerFactory
+ * @return the PersistenceManagerFactory instance
+ * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(String)
+ */
+ protected PersistenceManagerFactory newPersistenceManagerFactory(String name) {
+ return JDOHelper.getPersistenceManagerFactory(name, this.beanClassLoader);
+ }
+
+ /**
+ * Subclasses can override this to perform custom initialization of the
+ * PersistenceManagerFactory instance, creating it via the given Properties
+ * that got prepared by this LocalPersistenceManagerFactoryBean.
+ *
The default implementation invokes JDOHelper's
+ * getPersistenceManagerFactory(Map) method.
+ * A custom implementation could prepare the instance in a specific way,
+ * or use a custom PersistenceManagerFactory implementation.
+ * @param props the merged properties prepared by this LocalPersistenceManagerFactoryBean
+ * @return the PersistenceManagerFactory instance
+ * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(java.util.Map)
+ */
+ protected PersistenceManagerFactory newPersistenceManagerFactory(Map props) {
+ return JDOHelper.getPersistenceManagerFactory(props, this.beanClassLoader);
+ }
+
+
+ /**
+ * Return the singleton PersistenceManagerFactory.
+ */
+ public Object getObject() {
+ return this.persistenceManagerFactory;
+ }
+
+ public Class getObjectType() {
+ return (this.persistenceManagerFactory != null ?
+ this.persistenceManagerFactory.getClass() : PersistenceManagerFactory.class);
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+
+ /**
+ * Implementation of the PersistenceExceptionTranslator interface,
+ * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
+ *
Converts the exception if it is a JDOException, preferably using a specified
+ * JdoDialect. Else returns null to indicate an unknown exception.
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ * @see JdoDialect#translateException
+ * @see PersistenceManagerFactoryUtils#convertJdoAccessException
+ */
+ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+ if (ex instanceof JDOException) {
+ if (this.jdoDialect != null) {
+ return this.jdoDialect.translateException((JDOException) ex);
+ }
+ else {
+ return PersistenceManagerFactoryUtils.convertJdoAccessException((JDOException) ex);
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Close the PersistenceManagerFactory on bean factory shutdown.
+ */
+ public void destroy() {
+ logger.info("Closing JDO PersistenceManagerFactory");
+ this.persistenceManagerFactory.close();
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/PersistenceManagerFactoryUtils.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/PersistenceManagerFactoryUtils.java
new file mode 100644
index 00000000000..8d3a7ca4fe0
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/PersistenceManagerFactoryUtils.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jdo;
+
+import javax.jdo.JDODataStoreException;
+import javax.jdo.JDOException;
+import javax.jdo.JDOFatalDataStoreException;
+import javax.jdo.JDOFatalUserException;
+import javax.jdo.JDOObjectNotFoundException;
+import javax.jdo.JDOOptimisticVerificationException;
+import javax.jdo.JDOUserException;
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+import javax.jdo.Query;
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.Ordered;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
+import org.springframework.jdbc.support.SQLExceptionTranslator;
+import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
+import org.springframework.transaction.support.ResourceHolder;
+import org.springframework.transaction.support.ResourceHolderSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class featuring methods for JDO PersistenceManager handling,
+ * allowing for reuse of PersistenceManager instances within transactions.
+ * Also provides support for exception translation.
+ *
+ *
Used internally by {@link JdoTemplate}, {@link JdoInterceptor} and + * {@link JdoTransactionManager}. Can also be used directly in application code. + * + * @author Juergen Hoeller + * @since 03.06.2003 + * @see JdoTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.transaction.support.TransactionSynchronizationManager + */ +public abstract class PersistenceManagerFactoryUtils { + + /** + * Order value for TransactionSynchronization objects that clean up JDO + * PersistenceManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100 + * to execute PersistenceManager cleanup before JDBC Connection cleanup, if any. + * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER + */ + public static final int PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER = + DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100; + + private static final Log logger = LogFactory.getLog(PersistenceManagerFactoryUtils.class); + + + /** + * Create an appropriate SQLExceptionTranslator for the given PersistenceManagerFactory. + *
If a DataSource is found, creates a SQLErrorCodeSQLExceptionTranslator for the
+ * DataSource; else, falls back to a SQLStateSQLExceptionTranslator.
+ * @param connectionFactory the connection factory of the PersistenceManagerFactory
+ * (may be null)
+ * @return the SQLExceptionTranslator (never null)
+ * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
+ * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
+ * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
+ */
+ static SQLExceptionTranslator newJdbcExceptionTranslator(Object connectionFactory) {
+ // Check for PersistenceManagerFactory's DataSource.
+ if (connectionFactory instanceof DataSource) {
+ return new SQLErrorCodeSQLExceptionTranslator((DataSource) connectionFactory);
+ }
+ else {
+ return new SQLStateSQLExceptionTranslator();
+ }
+ }
+
+ /**
+ * Obtain a JDO PersistenceManager via the given factory. Is aware of a
+ * corresponding PersistenceManager bound to the current thread,
+ * for example when using JdoTransactionManager. Will create a new
+ * PersistenceManager else, if "allowCreate" is true.
+ * @param pmf PersistenceManagerFactory to create the PersistenceManager with
+ * @param allowCreate if a non-transactional PersistenceManager should be created
+ * when no transactional PersistenceManager can be found for the current thread
+ * @return the PersistenceManager
+ * @throws DataAccessResourceFailureException if the PersistenceManager couldn't be obtained
+ * @throws IllegalStateException if no thread-bound PersistenceManager found and
+ * "allowCreate" is false
+ * @see JdoTransactionManager
+ */
+ public static PersistenceManager getPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)
+ throws DataAccessResourceFailureException, IllegalStateException {
+
+ try {
+ return doGetPersistenceManager(pmf, allowCreate);
+ }
+ catch (JDOException ex) {
+ throw new DataAccessResourceFailureException("Could not obtain JDO PersistenceManager", ex);
+ }
+ }
+
+ /**
+ * Obtain a JDO PersistenceManager via the given factory. Is aware of a
+ * corresponding PersistenceManager bound to the current thread,
+ * for example when using JdoTransactionManager. Will create a new
+ * PersistenceManager else, if "allowCreate" is true.
+ *
Same as getPersistenceManager, but throwing the original JDOException.
+ * @param pmf PersistenceManagerFactory to create the PersistenceManager with
+ * @param allowCreate if a non-transactional PersistenceManager should be created
+ * when no transactional PersistenceManager can be found for the current thread
+ * @return the PersistenceManager
+ * @throws JDOException if the PersistenceManager couldn't be created
+ * @throws IllegalStateException if no thread-bound PersistenceManager found and
+ * "allowCreate" is false
+ * @see #getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
+ * @see JdoTransactionManager
+ */
+ public static PersistenceManager doGetPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)
+ throws JDOException, IllegalStateException {
+
+ Assert.notNull(pmf, "No PersistenceManagerFactory specified");
+
+ PersistenceManagerHolder pmHolder =
+ (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
+ if (pmHolder != null) {
+ if (!pmHolder.isSynchronizedWithTransaction() &&
+ TransactionSynchronizationManager.isSynchronizationActive()) {
+ pmHolder.setSynchronizedWithTransaction(true);
+ TransactionSynchronizationManager.registerSynchronization(
+ new PersistenceManagerSynchronization(pmHolder, pmf, false));
+ }
+ return pmHolder.getPersistenceManager();
+ }
+
+ if (!allowCreate && !TransactionSynchronizationManager.isSynchronizationActive()) {
+ throw new IllegalStateException("No JDO PersistenceManager bound to thread, " +
+ "and configuration does not allow creation of non-transactional one here");
+ }
+
+ logger.debug("Opening JDO PersistenceManager");
+ PersistenceManager pm = pmf.getPersistenceManager();
+
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ logger.debug("Registering transaction synchronization for JDO PersistenceManager");
+ // Use same PersistenceManager for further JDO actions within the transaction.
+ // Thread object will get removed by synchronization at transaction completion.
+ pmHolder = new PersistenceManagerHolder(pm);
+ pmHolder.setSynchronizedWithTransaction(true);
+ TransactionSynchronizationManager.registerSynchronization(
+ new PersistenceManagerSynchronization(pmHolder, pmf, true));
+ TransactionSynchronizationManager.bindResource(pmf, pmHolder);
+ }
+
+ return pm;
+ }
+
+ /**
+ * Return whether the given JDO PersistenceManager is transactional, that is,
+ * bound to the current thread by Spring's transaction facilities.
+ * @param pm the JDO PersistenceManager to check
+ * @param pmf JDO PersistenceManagerFactory that the PersistenceManager
+ * was created with (can be null)
+ * @return whether the PersistenceManager is transactional
+ */
+ public static boolean isPersistenceManagerTransactional(
+ PersistenceManager pm, PersistenceManagerFactory pmf) {
+
+ if (pmf == null) {
+ return false;
+ }
+ PersistenceManagerHolder pmHolder =
+ (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
+ return (pmHolder != null && pm == pmHolder.getPersistenceManager());
+ }
+
+ /**
+ * Apply the current transaction timeout, if any, to the given JDO Query object.
+ * @param query the JDO Query object
+ * @param pmf JDO PersistenceManagerFactory that the Query was created for
+ * @param jdoDialect the JdoDialect to use for applying a query timeout
+ * (must not be null)
+ * @throws JDOException if thrown by JDO methods
+ * @see JdoDialect#applyQueryTimeout
+ */
+ public static void applyTransactionTimeout(
+ Query query, PersistenceManagerFactory pmf, JdoDialect jdoDialect) throws JDOException {
+
+ Assert.notNull(query, "No Query object specified");
+ PersistenceManagerHolder pmHolder =
+ (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
+ if (pmHolder != null && pmHolder.hasTimeout()) {
+ jdoDialect.applyQueryTimeout(query, pmHolder.getTimeToLiveInSeconds());
+ }
+ }
+
+ /**
+ * Convert the given JDOException to an appropriate exception from the
+ * org.springframework.dao hierarchy.
+ *
The most important cases like object not found or optimistic locking
+ * failure are covered here. For more fine-granular conversion, JdoAccessor and
+ * JdoTransactionManager support sophisticated translation of exceptions via a
+ * JdoDialect.
+ * @param ex JDOException that occured
+ * @return the corresponding DataAccessException instance
+ * @see JdoAccessor#convertJdoAccessException
+ * @see JdoTransactionManager#convertJdoAccessException
+ * @see JdoDialect#translateException
+ */
+ public static DataAccessException convertJdoAccessException(JDOException ex) {
+ if (ex instanceof JDOObjectNotFoundException) {
+ throw new JdoObjectRetrievalFailureException((JDOObjectNotFoundException) ex);
+ }
+ if (ex instanceof JDOOptimisticVerificationException) {
+ throw new JdoOptimisticLockingFailureException((JDOOptimisticVerificationException) ex);
+ }
+ if (ex instanceof JDODataStoreException) {
+ return new JdoResourceFailureException((JDODataStoreException) ex);
+ }
+ if (ex instanceof JDOFatalDataStoreException) {
+ return new JdoResourceFailureException((JDOFatalDataStoreException) ex);
+ }
+ if (ex instanceof JDOUserException) {
+ return new JdoUsageException((JDOUserException) ex);
+ }
+ if (ex instanceof JDOFatalUserException) {
+ return new JdoUsageException((JDOFatalUserException) ex);
+ }
+ // fallback
+ return new JdoSystemException(ex);
+ }
+
+ /**
+ * Close the given PersistenceManager, created via the given factory,
+ * if it is not managed externally (i.e. not bound to the thread).
+ * @param pm PersistenceManager to close
+ * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
+ * (can be null)
+ */
+ public static void releasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf) {
+ try {
+ doReleasePersistenceManager(pm, pmf);
+ }
+ catch (JDOException ex) {
+ logger.debug("Could not close JDO PersistenceManager", ex);
+ }
+ catch (Throwable ex) {
+ logger.debug("Unexpected exception on closing JDO PersistenceManager", ex);
+ }
+ }
+
+ /**
+ * Actually release a PersistenceManager for the given factory.
+ * Same as releasePersistenceManager, but throwing the original JDOException.
+ * @param pm PersistenceManager to close
+ * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
+ * (can be null)
+ * @throws JDOException if thrown by JDO methods
+ */
+ public static void doReleasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf)
+ throws JDOException {
+
+ if (pm == null) {
+ return;
+ }
+ // Only release non-transactional PersistenceManagers.
+ if (!isPersistenceManagerTransactional(pm, pmf)) {
+ logger.debug("Closing JDO PersistenceManager");
+ pm.close();
+ }
+ }
+
+
+ /**
+ * Callback for resource cleanup at the end of a non-JDO transaction
+ * (e.g. when participating in a JtaTransactionManager transaction).
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ */
+ private static class PersistenceManagerSynchronization extends ResourceHolderSynchronization
+ implements Ordered {
+
+ private final boolean newPersistenceManager;
+
+ public PersistenceManagerSynchronization(
+ PersistenceManagerHolder pmHolder, PersistenceManagerFactory pmf, boolean newPersistenceManager) {
+ super(pmHolder, pmf);
+ this.newPersistenceManager = newPersistenceManager;
+ }
+
+ public int getOrder() {
+ return PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER;
+ }
+
+ protected boolean shouldUnbindAtCompletion() {
+ return this.newPersistenceManager;
+ }
+
+ protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
+ releasePersistenceManager(((PersistenceManagerHolder) resourceHolder).getPersistenceManager(),
+ (PersistenceManagerFactory) resourceKey);
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/PersistenceManagerHolder.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/PersistenceManagerHolder.java
new file mode 100644
index 00000000000..c261cb176d5
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/PersistenceManagerHolder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2007 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.jdo;
+
+import javax.jdo.PersistenceManager;
+
+import org.springframework.transaction.support.ResourceHolderSupport;
+import org.springframework.util.Assert;
+
+/**
+ * Holder wrapping a JDO PersistenceManager.
+ * JdoTransactionManager binds instances of this class
+ * to the thread, for a given PersistenceManagerFactory.
+ *
+ *
Note: This is an SPI class, not intended to be used by applications.
+ *
+ * @author Juergen Hoeller
+ * @since 03.06.2003
+ * @see JdoTransactionManager
+ * @see PersistenceManagerFactoryUtils
+ */
+public class PersistenceManagerHolder extends ResourceHolderSupport {
+
+ private final PersistenceManager persistenceManager;
+
+ private boolean transactionActive;
+
+
+ public PersistenceManagerHolder(PersistenceManager persistenceManager) {
+ Assert.notNull(persistenceManager, "PersistenceManager must not be null");
+ this.persistenceManager = persistenceManager;
+ }
+
+
+ public PersistenceManager getPersistenceManager() {
+ return this.persistenceManager;
+ }
+
+ protected void setTransactionActive(boolean transactionActive) {
+ this.transactionActive = transactionActive;
+ }
+
+ protected boolean isTransactionActive() {
+ return this.transactionActive;
+ }
+
+ public void clear() {
+ super.clear();
+ this.transactionActive = false;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/TransactionAwarePersistenceManagerFactoryProxy.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/TransactionAwarePersistenceManagerFactoryProxy.java
new file mode 100644
index 00000000000..753436c4edc
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/TransactionAwarePersistenceManagerFactoryProxy.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jdo;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Proxy for a target JDO {@link javax.jdo.PersistenceManagerFactory},
+ * returning the current thread-bound PersistenceManager (the Spring-managed
+ * transactional PersistenceManager or a the single OpenPersistenceManagerInView
+ * PersistenceManager) on getPersistenceManager(), if any.
+ *
+ *
Essentially, getPersistenceManager() calls get seamlessly
+ * forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}.
+ * Furthermore, PersistenceManager.close calls get forwarded to
+ * {@link PersistenceManagerFactoryUtils#releasePersistenceManager}.
+ *
+ *
The main advantage of this proxy is that it allows DAOs to work with a + * plain JDO PersistenceManagerFactory reference, while still participating in + * Spring's (or a J2EE server's) resource and transaction management. DAOs will + * only rely on the JDO API in such a scenario, without any Spring dependencies. + * + *
Note that the behavior of this proxy matches the behavior that the JDO spec + * defines for a PersistenceManagerFactory as exposed by a JCA connector, when + * deployed in a J2EE server. Hence, DAOs could seamlessly switch between a JNDI + * PersistenceManagerFactory and this proxy for a local PersistenceManagerFactory, + * receiving the reference through Dependency Injection. This will work without + * any Spring API dependencies in the DAO code! + * + *
It is usually preferable to write your JDO-based DAOs with Spring's + * {@link JdoTemplate}, offering benefits such as consistent data access + * exceptions instead of JDOExceptions at the DAO layer. However, Spring's + * resource and transaction management (and Dependency Injection) will work + * for DAOs written against the plain JDO API as well. + * + *
Of course, you can still access the target PersistenceManagerFactory + * even when your DAOs go through this proxy, by defining a bean reference + * that points directly at your target PersistenceManagerFactory bean. + * + * @author Juergen Hoeller + * @since 1.2 + * @see javax.jdo.PersistenceManagerFactory#getPersistenceManager() + * @see javax.jdo.PersistenceManager#close() + * @see PersistenceManagerFactoryUtils#getPersistenceManager + * @see PersistenceManagerFactoryUtils#releasePersistenceManager + */ +public class TransactionAwarePersistenceManagerFactoryProxy implements FactoryBean { + + private PersistenceManagerFactory target; + + private boolean allowCreate = true; + + private PersistenceManagerFactory proxy; + + + /** + * Set the target JDO PersistenceManagerFactory that this proxy should + * delegate to. This should be the raw PersistenceManagerFactory, as + * accessed by JdoTransactionManager. + * @see org.springframework.orm.jdo.JdoTransactionManager + */ + public void setTargetPersistenceManagerFactory(PersistenceManagerFactory target) { + Assert.notNull(target, "Target PersistenceManagerFactory must not be null"); + this.target = target; + Class[] ifcs = ClassUtils.getAllInterfacesForClass(target.getClass(), getClass().getClassLoader()); + this.proxy = (PersistenceManagerFactory) Proxy.newProxyInstance( + target.getClass().getClassLoader(), ifcs, new TransactionAwareFactoryInvocationHandler()); + } + + /** + * Return the target JDO PersistenceManagerFactory that this proxy delegates to. + */ + public PersistenceManagerFactory getTargetPersistenceManagerFactory() { + return this.target; + } + + /** + * Set whether the PersistenceManagerFactory proxy is allowed to create + * a non-transactional PersistenceManager when no transactional + * PersistenceManager can be found for the current thread. + *
Default is "true". Can be turned off to enforce access to
+ * transactional PersistenceManagers, which safely allows for DAOs
+ * written to get a PersistenceManager without explicit closing
+ * (i.e. a PersistenceManagerFactory.getPersistenceManager()
+ * call without corresponding PersistenceManager.close() call).
+ * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
+ */
+ public void setAllowCreate(boolean allowCreate) {
+ this.allowCreate = allowCreate;
+ }
+
+ /**
+ * Return whether the PersistenceManagerFactory proxy is allowed to create
+ * a non-transactional PersistenceManager when no transactional
+ * PersistenceManager can be found for the current thread.
+ */
+ protected boolean isAllowCreate() {
+ return this.allowCreate;
+ }
+
+
+ public Object getObject() {
+ return this.proxy;
+ }
+
+ public Class getObjectType() {
+ return PersistenceManagerFactory.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+
+ /**
+ * Invocation handler that delegates getPersistenceManager calls on the
+ * PersistenceManagerFactory proxy to PersistenceManagerFactoryUtils
+ * for being aware of thread-bound transactions.
+ */
+ private class TransactionAwareFactoryInvocationHandler implements InvocationHandler {
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Invocation on PersistenceManagerFactory interface coming in...
+
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of PersistenceManagerFactory proxy.
+ return new Integer(System.identityHashCode(proxy));
+ }
+ else if (method.getName().equals("getPersistenceManager")) {
+ PersistenceManagerFactory target = getTargetPersistenceManagerFactory();
+ PersistenceManager pm =
+ PersistenceManagerFactoryUtils.doGetPersistenceManager(target, isAllowCreate());
+ Class[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), getClass().getClassLoader());
+ return (PersistenceManager) Proxy.newProxyInstance(
+ pm.getClass().getClassLoader(), ifcs, new TransactionAwareInvocationHandler(pm, target));
+ }
+
+ // Invoke method on target PersistenceManagerFactory.
+ try {
+ return method.invoke(getTargetPersistenceManagerFactory(), args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+
+ /**
+ * Invocation handler that delegates close calls on PersistenceManagers to
+ * PersistenceManagerFactoryUtils for being aware of thread-bound transactions.
+ */
+ private static class TransactionAwareInvocationHandler implements InvocationHandler {
+
+ private final PersistenceManager target;
+
+ private final PersistenceManagerFactory persistenceManagerFactory;
+
+ public TransactionAwareInvocationHandler(PersistenceManager target, PersistenceManagerFactory pmf) {
+ this.target = target;
+ this.persistenceManagerFactory = pmf;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Invocation on PersistenceManager interface coming in...
+
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of PersistenceManager proxy.
+ return new Integer(System.identityHashCode(proxy));
+ }
+ else if (method.getName().equals("close")) {
+ // Handle close method: only close if not within a transaction.
+ if (this.persistenceManagerFactory != null) {
+ PersistenceManagerFactoryUtils.doReleasePersistenceManager(
+ this.target, this.persistenceManagerFactory);
+ }
+ return null;
+ }
+
+ // Invoke method on target PersistenceManager.
+ try {
+ return method.invoke(this.target, args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/package.html
new file mode 100644
index 00000000000..69de760e087
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/package.html
@@ -0,0 +1,9 @@
+
+
Requires a PersistenceManagerFactory to be set, providing a JdoTemplate + * based on it to subclasses. Can alternatively be initialized directly with a + * JdoTemplate, to reuse the latter's settings such as the PersistenceManagerFactory, + * JdoDialect, flush mode, etc. + * + *
This base class is mainly intended for JdoTemplate usage but can also
+ * be used when working with PersistenceManagerFactoryUtils directly, for example
+ * in combination with JdoInterceptor-managed PersistenceManagers. Convenience
+ * getPersistenceManager and releasePersistenceManager
+ * methods are provided for that usage style.
+ *
+ *
This class will create its own JdoTemplate if only a PersistenceManagerFactory
+ * is passed in. The "allowCreate" flag on that JdoTemplate will be "true" by default.
+ * A custom JdoTemplate instance can be used through overriding createJdoTemplate.
+ *
+ * @author Juergen Hoeller
+ * @since 28.07.2003
+ * @see #setPersistenceManagerFactory
+ * @see #setJdoTemplate
+ * @see #createJdoTemplate
+ * @see #getPersistenceManager
+ * @see #releasePersistenceManager
+ * @see org.springframework.orm.jdo.JdoTemplate
+ * @see org.springframework.orm.jdo.JdoInterceptor
+ */
+public abstract class JdoDaoSupport extends DaoSupport {
+
+ private JdoTemplate jdoTemplate;
+
+
+ /**
+ * Set the JDO PersistenceManagerFactory to be used by this DAO.
+ * Will automatically create a JdoTemplate for the given PersistenceManagerFactory.
+ * @see #createJdoTemplate
+ * @see #setJdoTemplate
+ */
+ public final void setPersistenceManagerFactory(PersistenceManagerFactory persistenceManagerFactory) {
+ if (this.jdoTemplate == null || persistenceManagerFactory != this.jdoTemplate.getPersistenceManagerFactory()) {
+ this.jdoTemplate = createJdoTemplate(persistenceManagerFactory);
+ }
+ }
+
+ /**
+ * Create a JdoTemplate for the given PersistenceManagerFactory.
+ * Only invoked if populating the DAO with a PersistenceManagerFactory reference!
+ *
Can be overridden in subclasses to provide a JdoTemplate instance + * with different configuration, or a custom JdoTemplate subclass. + * @param persistenceManagerFactory the JDO PersistenceManagerFactoryto create a JdoTemplate for + * @return the new JdoTemplate instance + * @see #setPersistenceManagerFactory + */ + protected JdoTemplate createJdoTemplate(PersistenceManagerFactory persistenceManagerFactory) { + return new JdoTemplate(persistenceManagerFactory); + } + + /** + * Return the JDO PersistenceManagerFactory used by this DAO. + */ + public final PersistenceManagerFactory getPersistenceManagerFactory() { + return (this.jdoTemplate != null ? this.jdoTemplate.getPersistenceManagerFactory() : null); + } + + /** + * Set the JdoTemplate for this DAO explicitly, + * as an alternative to specifying a PersistenceManagerFactory. + * @see #setPersistenceManagerFactory + */ + public final void setJdoTemplate(JdoTemplate jdoTemplate) { + this.jdoTemplate = jdoTemplate; + } + + /** + * Return the JdoTemplate for this DAO, pre-initialized + * with the PersistenceManagerFactory or set explicitly. + */ + public final JdoTemplate getJdoTemplate() { + return jdoTemplate; + } + + protected final void checkDaoConfig() { + if (this.jdoTemplate == null) { + throw new IllegalArgumentException("persistenceManagerFactory or jdoTemplate is required"); + } + } + + + /** + * Get a JDO PersistenceManager, either from the current transaction or + * a new one. The latter is only allowed if the "allowCreate" setting + * of this bean's JdoTemplate is true. + * @return the JDO PersistenceManager + * @throws DataAccessResourceFailureException if the PersistenceManager couldn't be created + * @throws IllegalStateException if no thread-bound PersistenceManager found and allowCreate false + * @see org.springframework.orm.jdo.PersistenceManagerFactoryUtils#getPersistenceManager + */ + protected final PersistenceManager getPersistenceManager() { + return getPersistenceManager(this.jdoTemplate.isAllowCreate()); + } + + /** + * Get a JDO PersistenceManager, either from the current transaction or + * a new one. The latter is only allowed if "allowCreate" is true. + * @param allowCreate if a non-transactional PersistenceManager should be created + * when no transactional PersistenceManager can be found for the current thread + * @return the JDO PersistenceManager + * @throws DataAccessResourceFailureException if the PersistenceManager couldn't be created + * @throws IllegalStateException if no thread-bound PersistenceManager found and allowCreate false + * @see org.springframework.orm.jdo.PersistenceManagerFactoryUtils#getPersistenceManager + */ + protected final PersistenceManager getPersistenceManager(boolean allowCreate) + throws DataAccessResourceFailureException, IllegalStateException { + + return PersistenceManagerFactoryUtils.getPersistenceManager(getPersistenceManagerFactory(), allowCreate); + } + + /** + * Convert the given JDOException to an appropriate exception from the + * org.springframework.dao hierarchy. + *
Delegates to the convertJdoAccessException method of this DAO's JdoTemplate. + * @param ex JDOException that occured + * @return the corresponding DataAccessException instance + * @see #setJdoTemplate + * @see org.springframework.orm.jdo.JdoTemplate#convertJdoAccessException + */ + protected final DataAccessException convertJdoAccessException(JDOException ex) { + return this.jdoTemplate.convertJdoAccessException(ex); + } + + /** + * Close the given JDO PersistenceManager, created via this DAO's + * PersistenceManagerFactory, if it isn't bound to the thread. + * @param pm PersistenceManager to close + * @see org.springframework.orm.jdo.PersistenceManagerFactoryUtils#releasePersistenceManager + */ + protected final void releasePersistenceManager(PersistenceManager pm) { + PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory()); + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/OpenPersistenceManagerInViewFilter.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/OpenPersistenceManagerInViewFilter.java new file mode 100644 index 00000000000..e4343a67fd1 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/OpenPersistenceManagerInViewFilter.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2007 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.jdo.support; + +import java.io.IOException; + +import javax.jdo.PersistenceManager; +import javax.jdo.PersistenceManagerFactory; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.orm.jdo.PersistenceManagerFactoryUtils; +import org.springframework.orm.jdo.PersistenceManagerHolder; +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 JDO PersistenceManager to the thread for the + * entire processing of the request. Intended for the "Open PersistenceManager in + * View" pattern, i.e. to allow for lazy loading in web views despite the + * original transactions already being completed. + * + *
This filter makes JDO PersistenceManagers available via the current thread, + * which will be autodetected by transaction managers. It is suitable for service + * layer transactions via {@link org.springframework.orm.jdo.JdoTransactionManager} + * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well + * as for non-transactional read-only execution. + * + *
Looks up the PersistenceManagerFactory in Spring's root web application context.
+ * Supports a "persistenceManagerFactoryBeanName" filter init-param in web.xml;
+ * the default bean name is "persistenceManagerFactory". Looks up the PersistenceManagerFactory
+ * on each request, to avoid initialization order issues (when using ContextLoaderServlet,
+ * the root application context will get initialized after this filter).
+ *
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see OpenPersistenceManagerInViewInterceptor
+ * @see org.springframework.orm.jdo.JdoInterceptor
+ * @see org.springframework.orm.jdo.JdoTransactionManager
+ * @see org.springframework.orm.jdo.PersistenceManagerFactoryUtils#getPersistenceManager
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public class OpenPersistenceManagerInViewFilter extends OncePerRequestFilter {
+
+ public static final String DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME = "persistenceManagerFactory";
+
+ private String persistenceManagerFactoryBeanName = DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME;
+
+
+ /**
+ * Set the bean name of the PersistenceManagerFactory to fetch from Spring's
+ * root application context. Default is "persistenceManagerFactory".
+ * @see #DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME
+ */
+ public void setPersistenceManagerFactoryBeanName(String persistenceManagerFactoryBeanName) {
+ this.persistenceManagerFactoryBeanName = persistenceManagerFactoryBeanName;
+ }
+
+ /**
+ * Return the bean name of the PersistenceManagerFactory to fetch from Spring's
+ * root application context.
+ */
+ protected String getPersistenceManagerFactoryBeanName() {
+ return this.persistenceManagerFactoryBeanName;
+ }
+
+
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ PersistenceManagerFactory pmf = lookupPersistenceManagerFactory(request);
+ boolean participate = false;
+
+ if (TransactionSynchronizationManager.hasResource(pmf)) {
+ // Do not modify the PersistenceManager: just set the participate flag.
+ participate = true;
+ }
+ else {
+ logger.debug("Opening JDO PersistenceManager in OpenPersistenceManagerInViewFilter");
+ PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(pmf, true);
+ TransactionSynchronizationManager.bindResource(pmf, new PersistenceManagerHolder(pm));
+ }
+
+ try {
+ filterChain.doFilter(request, response);
+ }
+
+ finally {
+ if (!participate) {
+ PersistenceManagerHolder pmHolder = (PersistenceManagerHolder)
+ TransactionSynchronizationManager.unbindResource(pmf);
+ logger.debug("Closing JDO PersistenceManager in OpenPersistenceManagerInViewFilter");
+ PersistenceManagerFactoryUtils.releasePersistenceManager(pmHolder.getPersistenceManager(), pmf);
+ }
+ }
+ }
+
+ /**
+ * Look up the PersistenceManagerFactory that this filter should use,
+ * taking the current HTTP request as argument.
+ *
Default implementation delegates to the lookupPersistenceManagerFactory
+ * without arguments.
+ * @return the PersistenceManagerFactory to use
+ * @see #lookupPersistenceManagerFactory()
+ */
+ protected PersistenceManagerFactory lookupPersistenceManagerFactory(HttpServletRequest request) {
+ return lookupPersistenceManagerFactory();
+ }
+
+ /**
+ * Look up the PersistenceManagerFactory that this filter should use.
+ * The default implementation looks for a bean with the specified name
+ * in Spring's root application context.
+ * @return the PersistenceManagerFactory to use
+ * @see #getPersistenceManagerFactoryBeanName
+ */
+ protected PersistenceManagerFactory lookupPersistenceManagerFactory() {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using PersistenceManagerFactory '" + getPersistenceManagerFactoryBeanName() +
+ "' for OpenPersistenceManagerInViewFilter");
+ }
+ WebApplicationContext wac =
+ WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
+ return (PersistenceManagerFactory)
+ wac.getBean(getPersistenceManagerFactoryBeanName(), PersistenceManagerFactory.class);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/OpenPersistenceManagerInViewInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/OpenPersistenceManagerInViewInterceptor.java
new file mode 100644
index 00000000000..91c40af077f
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/OpenPersistenceManagerInViewInterceptor.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2007 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.jdo.support;
+
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.orm.jdo.PersistenceManagerFactoryUtils;
+import org.springframework.orm.jdo.PersistenceManagerHolder;
+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 JDO PersistenceManager to the
+ * thread for the entire processing of the request. Intended for the "Open
+ * PersistenceManager in View" pattern, i.e. to allow for lazy loading in
+ * web views despite the original transactions already being completed.
+ *
+ *
This interceptor makes JDO PersistenceManagers available via the current thread, + * which will be autodetected by transaction managers. It is suitable for service + * layer transactions via {@link org.springframework.orm.jdo.JdoTransactionManager} + * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well + * as for non-transactional read-only execution. + * + *
In contrast to {@link OpenPersistenceManagerInViewFilter}, this interceptor + * is set up in a Spring application context and can thus take advantage of + * bean wiring. It inherits common JDO configuration properties from + * {@link org.springframework.orm.jdo.JdoAccessor}, to be configured in a + * bean definition. + * + * @author Juergen Hoeller + * @since 1.1 + * @see OpenPersistenceManagerInViewFilter + * @see org.springframework.orm.jdo.JdoInterceptor + * @see org.springframework.orm.jdo.JdoTransactionManager + * @see org.springframework.orm.jdo.PersistenceManagerFactoryUtils#getPersistenceManager + * @see org.springframework.transaction.support.TransactionSynchronizationManager + */ +public class OpenPersistenceManagerInViewInterceptor implements WebRequestInterceptor { + + /** + * Suffix that gets appended to the PersistenceManagerFactory toString + * representation for the "participate in existing persistence manager + * handling" request attribute. + * @see #getParticipateAttributeName + */ + public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE"; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private PersistenceManagerFactory persistenceManagerFactory; + + + /** + * Set the JDO PersistenceManagerFactory that should be used to create + * PersistenceManagers. + */ + public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { + this.persistenceManagerFactory = pmf; + } + + /** + * Return the JDO PersistenceManagerFactory that should be used to create + * PersistenceManagers. + */ + public PersistenceManagerFactory getPersistenceManagerFactory() { + return persistenceManagerFactory; + } + + + public void preHandle(WebRequest request) throws DataAccessException { + if (TransactionSynchronizationManager.hasResource(getPersistenceManagerFactory())) { + // Do not modify the PersistenceManager: just mark the request accordingly. + String participateAttributeName = getParticipateAttributeName(); + Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + int newCount = (count != null) ? count.intValue() + 1 : 1; + request.setAttribute(getParticipateAttributeName(), new Integer(newCount), WebRequest.SCOPE_REQUEST); + } + else { + logger.debug("Opening JDO PersistenceManager in OpenPersistenceManagerInViewInterceptor"); + PersistenceManager pm = + PersistenceManagerFactoryUtils.getPersistenceManager(getPersistenceManagerFactory(), true); + TransactionSynchronizationManager.bindResource( + getPersistenceManagerFactory(), new PersistenceManagerHolder(pm)); + } + } + + public void postHandle(WebRequest request, ModelMap model) { + } + + 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 PersistenceManager: just clear the marker. + if (count.intValue() > 1) { + request.setAttribute(participateAttributeName, new Integer(count.intValue() - 1), WebRequest.SCOPE_REQUEST); + } + else { + request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + } + } + else { + PersistenceManagerHolder pmHolder = (PersistenceManagerHolder) + TransactionSynchronizationManager.unbindResource(getPersistenceManagerFactory()); + logger.debug("Closing JDO PersistenceManager in OpenPersistenceManagerInViewInterceptor"); + PersistenceManagerFactoryUtils.releasePersistenceManager( + pmHolder.getPersistenceManager(), getPersistenceManagerFactory()); + } + } + + /** + * Return the name of the request attribute that identifies that a request is + * already filtered. Default implementation takes the toString representation + * of the PersistenceManagerFactory instance and appends ".FILTERED". + * @see #PARTICIPATE_SUFFIX + */ + protected String getParticipateAttributeName() { + return getPersistenceManagerFactory().toString() + PARTICIPATE_SUFFIX; + } + +} diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/package.html new file mode 100644 index 00000000000..a220f24ff73 --- /dev/null +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jdo/support/package.html @@ -0,0 +1,8 @@ + +
+ +Classes supporting theorg.springframework.orm.jdo package.
+Contains a DAO base class for JdoTemplate usage.
+
+
+
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
new file mode 100644
index 00000000000..0945d2587f0
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+import javax.persistence.spi.PersistenceProvider;
+import javax.persistence.spi.PersistenceUnitInfo;
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Abstract {@link org.springframework.beans.factory.FactoryBean} that
+ * creates a local JPA {@link javax.persistence.EntityManagerFactory}
+ * instance within a Spring application context.
+ *
+ * Encapsulates the common functionality between the different JPA + * bootstrap contracts (standalone as well as container). + * + *
Implements support for standard JPA configuration as well as + * Spring's {@link JpaVendorAdapter} abstraction, and controls the + * EntityManagerFactory's lifecycle. + * + *
This class also implements the + * {@link org.springframework.dao.support.PersistenceExceptionTranslator} + * interface, as autodetected by Spring's + * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}, + * for AOP-based translation of native exceptions to Spring DataAccessExceptions. + * Hence, the presence of e.g. LocalEntityManagerFactoryBean automatically enables + * a PersistenceExceptionTranslationPostProcessor to translate JPA exceptions. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 2.0 + * @see LocalEntityManagerFactoryBean + * @see LocalContainerEntityManagerFactoryBean + */ +public abstract class AbstractEntityManagerFactoryBean implements + FactoryBean, BeanClassLoaderAware, InitializingBean, DisposableBean, + EntityManagerFactoryInfo, PersistenceExceptionTranslator { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private PersistenceProvider persistenceProvider; + + private String persistenceUnitName; + + private final Map jpaPropertyMap = new HashMap(); + + private Class extends EntityManagerFactory> entityManagerFactoryInterface; + + private Class extends EntityManager> entityManagerInterface; + + private JpaDialect jpaDialect; + + private JpaVendorAdapter jpaVendorAdapter; + + private ClassLoader beanClassLoader = getClass().getClassLoader(); + + /** Raw EntityManagerFactory as returned by the PersistenceProvider */ + public EntityManagerFactory nativeEntityManagerFactory; + + private EntityManagerFactory entityManagerFactory; + + + /** + * Set the PersistenceProvider implementation class to use for creating the + * EntityManagerFactory. If not specified, the persistence provider will be + * taken from the JpaVendorAdapter (if any) or retrieved through scanning + * (as far as possible). + * @see JpaVendorAdapter#getPersistenceProvider() + * @see javax.persistence.spi.PersistenceProvider + * @see javax.persistence.Persistence + */ + public void setPersistenceProviderClass(Class extends PersistenceProvider> persistenceProviderClass) { + Assert.isAssignable(PersistenceProvider.class, persistenceProviderClass); + this.persistenceProvider = (PersistenceProvider) BeanUtils.instantiateClass(persistenceProviderClass); + } + + /** + * Set the PersistenceProvider instance to use for creating the + * EntityManagerFactory. If not specified, the persistence provider + * will be taken from the JpaVendorAdapter (if any) or determined + * by the persistence unit deployment descriptor (as far as possible). + * @see JpaVendorAdapter#getPersistenceProvider() + * @see javax.persistence.spi.PersistenceProvider + * @see javax.persistence.Persistence + */ + public void setPersistenceProvider(PersistenceProvider persistenceProvider) { + this.persistenceProvider = persistenceProvider; + } + + public PersistenceProvider getPersistenceProvider() { + return this.persistenceProvider; + } + + /** + * Specify the name of the EntityManagerFactory configuration. + *
Default is none, indicating the default EntityManagerFactory
+ * configuration. The persistence provider will throw an exception if
+ * ambiguous EntityManager configurations are found.
+ * @see javax.persistence.Persistence#createEntityManagerFactory(String)
+ */
+ public void setPersistenceUnitName(String persistenceUnitName) {
+ this.persistenceUnitName = persistenceUnitName;
+ }
+
+ public String getPersistenceUnitName() {
+ return this.persistenceUnitName;
+ }
+
+ /**
+ * Specify JPA properties, to be passed into
+ * Persistence.createEntityManagerFactory (if any).
+ *
Can be populated with a String "value" (parsed via PropertiesEditor) or a
+ * "props" element in XML bean definitions.
+ * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
+ * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
+ */
+ public void setJpaProperties(Properties jpaProperties) {
+ CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
+ }
+
+ /**
+ * Specify JPA properties as a Map, to be passed into
+ * Persistence.createEntityManagerFactory (if any).
+ *
Can be populated with a "map" or "props" element in XML bean definitions. + * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map) + * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map) + */ + public void setJpaPropertyMap(Map jpaProperties) { + if (jpaProperties != null) { + this.jpaPropertyMap.putAll(jpaProperties); + } + } + + /** + * Allow Map access to the JPA properties to be passed to the persistence + * provider, with the option to add or override specific entries. + *
Useful for specifying entries directly, for example via + * "jpaPropertyMap[myKey]". + */ + public Map getJpaPropertyMap() { + return this.jpaPropertyMap; + } + + /** + * Specify the (potentially vendor-specific) EntityManagerFactory interface + * that this EntityManagerFactory proxy is supposed to implement. + *
The default will be taken from the specific JpaVendorAdapter, if any,
+ * or set to the standard javax.persistence.EntityManagerFactory
+ * interface else.
+ * @see JpaVendorAdapter#getEntityManagerFactoryInterface()
+ */
+ public void setEntityManagerFactoryInterface(Class extends EntityManagerFactory> emfInterface) {
+ Assert.isAssignable(EntityManagerFactory.class, emfInterface);
+ this.entityManagerFactoryInterface = emfInterface;
+ }
+
+ /**
+ * Specify the (potentially vendor-specific) EntityManager interface
+ * that this factory's EntityManagers are supposed to implement.
+ *
The default will be taken from the specific JpaVendorAdapter, if any,
+ * or set to the standard Uses the dialect's conversion if possible; otherwise falls back to
+ * standard JPA exception conversion.
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ * @see JpaDialect#translateExceptionIfPossible
+ * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
+ */
+ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+ return (this.jpaDialect != null ? this.jpaDialect.translateExceptionIfPossible(ex) :
+ EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex));
+ }
+
+ public EntityManagerFactory getNativeEntityManagerFactory() {
+ return this.nativeEntityManagerFactory;
+ }
+
+ public PersistenceUnitInfo getPersistenceUnitInfo() {
+ return null;
+ }
+
+ public DataSource getDataSource() {
+ return null;
+ }
+
+
+ /**
+ * Return the singleton EntityManagerFactory.
+ */
+ public EntityManagerFactory getObject() {
+ return this.entityManagerFactory;
+ }
+
+ public Class getObjectType() {
+ return (this.entityManagerFactory != null ? this.entityManagerFactory.getClass() : EntityManagerFactory.class);
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+
+ /**
+ * Close the EntityManagerFactory on bean factory shutdown.
+ */
+ public void destroy() {
+ if (logger.isInfoEnabled()) {
+ logger.info("Closing JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
+ }
+ this.entityManagerFactory.close();
+ }
+
+
+ /**
+ * Dynamic proxy invocation handler proxying an EntityManagerFactory to
+ * return a proxy EntityManager if necessary from createEntityManager()
+ * methods.
+ */
+ private static class ManagedEntityManagerFactoryInvocationHandler implements InvocationHandler {
+
+ private final EntityManagerFactory targetEntityManagerFactory;
+
+ private final EntityManagerFactoryInfo entityManagerFactoryInfo;
+
+ private final EntityManagerFactoryPlusOperations entityManagerFactoryPlusOperations;
+
+ public ManagedEntityManagerFactoryInvocationHandler(EntityManagerFactory targetEmf,
+ EntityManagerFactoryInfo emfInfo, EntityManagerFactoryPlusOperations entityManagerFactoryPlusOperations) {
+
+ this.targetEntityManagerFactory = targetEmf;
+ this.entityManagerFactoryInfo = emfInfo;
+ this.entityManagerFactoryPlusOperations = entityManagerFactoryPlusOperations;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of EntityManagerFactory proxy.
+ return new Integer(System.identityHashCode(proxy));
+ }
+ else if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
+ return method.invoke(this.entityManagerFactoryInfo, args);
+ }
+ else if (method.getDeclaringClass().equals(EntityManagerFactoryPlusOperations.class)) {
+ return method.invoke(this.entityManagerFactoryPlusOperations, args);
+ }
+
+ Object retVal = method.invoke(this.targetEntityManagerFactory, args);
+ if (retVal instanceof EntityManager) {
+ EntityManager rawEntityManager = (EntityManager) retVal;
+ retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(
+ rawEntityManager, this.entityManagerFactoryInfo);
+ }
+ return retVal;
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java
new file mode 100644
index 00000000000..6e3aab31551
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2007 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.jpa;
+
+import java.io.Serializable;
+import java.sql.SQLException;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.datasource.ConnectionHandle;
+import org.springframework.transaction.InvalidIsolationLevelException;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+
+/**
+ * Default implementation of the {@link JpaDialect} interface.
+ * Used as default dialect by {@link JpaAccessor} and {@link JpaTransactionManager}.
+ *
+ * Simply begins a standard JPA transaction in {@link #beginTransaction}
+ * and performs standard exception translation through {@link EntityManagerFactoryUtils}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see JpaAccessor#setJpaDialect
+ * @see JpaTransactionManager#setJpaDialect
+ */
+public class DefaultJpaDialect implements JpaDialect, Serializable {
+
+ //-------------------------------------------------------------------------
+ // Hooks for transaction management (used by JpaTransactionManager)
+ //-------------------------------------------------------------------------
+
+ /**
+ * This implementation invokes the standard JPA This implementation does not return any transaction data Object, since there
+ * is no state to be kept for a standard JPA transaction. Hence, subclasses do not
+ * have to care about the return value ( If the JPA implementation returns a Connection handle that it expects
+ * the application to close after use, the dialect implementation needs to invoke
+ * Not intended to be used directly. See {@link JpaAccessor}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see JpaAccessor
+ * @see EntityManagerFactoryUtils
+ */
+public abstract class EntityManagerFactoryAccessor {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private EntityManagerFactory entityManagerFactory;
+
+ private final Map jpaPropertyMap = new HashMap();
+
+
+ /**
+ * Set the JPA EntityManagerFactory that should be used to create
+ * EntityManagers.
+ * @see javax.persistence.EntityManagerFactory#createEntityManager()
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ public void setEntityManagerFactory(EntityManagerFactory emf) {
+ this.entityManagerFactory = emf;
+ }
+
+ /**
+ * Return the JPA EntityManagerFactory that should be used to create
+ * EntityManagers.
+ */
+ public EntityManagerFactory getEntityManagerFactory() {
+ return this.entityManagerFactory;
+ }
+
+ /**
+ * Specify JPA properties, to be passed into
+ * Can be populated with a String "value" (parsed via PropertiesEditor)
+ * or a "props" element in XML bean definitions.
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ public void setJpaProperties(Properties jpaProperties) {
+ CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
+ }
+
+ /**
+ * Specify JPA properties as a Map, to be passed into
+ * Can be populated with a "map" or "props" element in XML bean definitions.
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ public void setJpaPropertyMap(Map jpaProperties) {
+ if (jpaProperties != null) {
+ this.jpaPropertyMap.putAll(jpaProperties);
+ }
+ }
+
+ /**
+ * Allow Map access to the JPA properties to be passed to the persistence
+ * provider, with the option to add or override specific entries.
+ * Useful for specifying entries directly, for example via "jpaPropertyMap[myKey]".
+ */
+ public Map getJpaPropertyMap() {
+ return this.jpaPropertyMap;
+ }
+
+
+ /**
+ * Obtain a new EntityManager from this accessor's EntityManagerFactory.
+ * Can be overridden in subclasses to create specific EntityManager variants.
+ * @return a new EntityManager
+ * @throws IllegalStateException if this accessor is not configured with an EntityManagerFactory
+ * @see javax.persistence.EntityManagerFactory#createEntityManager()
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ protected EntityManager createEntityManager() throws IllegalStateException {
+ EntityManagerFactory emf = getEntityManagerFactory();
+ Assert.state(emf != null, "No EntityManagerFactory specified");
+ Map properties = getJpaPropertyMap();
+ return (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
+ }
+
+ /**
+ * Obtain the transactional EntityManager for this accessor's EntityManagerFactory, if any.
+ * @return the transactional EntityManager, or This facility can be obtained from Spring-managed EntityManagerFactory
+ * proxies through casting the EntityManagerFactory handle to this interface.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public interface EntityManagerFactoryInfo {
+
+ /**
+ * Return the raw underlying EntityManagerFactory.
+ * @return the unadorned EntityManagerFactory (never A Proxies will be generated in this ClassLoader.
+ */
+ ClassLoader getBeanClassLoader();
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryPlus.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryPlus.java
new file mode 100644
index 00000000000..9af3079dfed
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryPlus.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+import javax.persistence.EntityManagerFactory;
+
+/**
+ * Extension of the standard JPA EntityManagerFactory interface, linking in
+ * Spring's EntityManagerFactoryPlusOperations interface which defines
+ * additional operations (beyond JPA 1.0) in a vendor-independent fashion.
+ *
+ * @author Rod Johnson
+ * @since 2.0
+ * @see javax.persistence.EntityManager
+ */
+public interface EntityManagerFactoryPlus extends EntityManagerFactory, EntityManagerFactoryPlusOperations {
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryPlusOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryPlusOperations.java
new file mode 100644
index 00000000000..554fbdf9837
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryPlusOperations.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+/**
+ * Interface that defines common operations beyond the standard
+ * JPA EntityManagerFactory interface, in a vendor-independent fashion.
+ * To be adapted to specific JPA providers through a JpaDialect.
+ *
+ * As of Spring 2.0, this interface does not define any operations yet.
+ * The pass-through mechanism to the underlying JpaDialect is already in
+ * place. Concrete operations will be added in the Spring 2.5 timeframe.
+ *
+ * @author Rod Johnson
+ * @since 2.0
+ * @see JpaDialect#getEntityManagerPlusOperations
+ * @see javax.persistence.EntityManagerFactory
+ */
+public interface EntityManagerFactoryPlusOperations {
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
new file mode 100644
index 00000000000..0141505727b
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.util.Map;
+
+import javax.persistence.EntityExistsException;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityNotFoundException;
+import javax.persistence.NoResultException;
+import javax.persistence.NonUniqueResultException;
+import javax.persistence.OptimisticLockException;
+import javax.persistence.PersistenceException;
+import javax.persistence.TransactionRequiredException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.core.Ordered;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.transaction.support.ResourceHolder;
+import org.springframework.transaction.support.ResourceHolderSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Helper class featuring methods for JPA EntityManager handling,
+ * allowing for reuse of EntityManager instances within transactions.
+ * Also provides support for exception translation.
+ *
+ * Mainly intended for internal use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class EntityManagerFactoryUtils {
+
+ /**
+ * Order value for TransactionSynchronization objects that clean up JPA
+ * EntityManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
+ * to execute EntityManager cleanup before JDBC Connection cleanup, if any.
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
+ */
+ public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER =
+ DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
+
+ private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class);
+
+
+ /**
+ * Find an EntityManagerFactory with the given name in the given
+ * Spring application context (represented as ListableBeanFactory).
+ * The specified unit name will be matched against the configured
+ * peristence unit, provided that a discovered EntityManagerFactory
+ * implements the {@link EntityManagerFactoryInfo} interface. If not,
+ * the persistence unit name will be matched against the Spring bean name,
+ * assuming that the EntityManagerFactory bean names follow that convention.
+ * @param beanFactory the ListableBeanFactory to search
+ * @param unitName the name of the persistence unit (never empty)
+ * @return the EntityManagerFactory
+ * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory in the context
+ * @see EntityManagerFactoryInfo#getPersistenceUnitName()
+ */
+ public static EntityManagerFactory findEntityManagerFactory(
+ ListableBeanFactory beanFactory, String unitName) throws NoSuchBeanDefinitionException {
+
+ Assert.notNull(beanFactory, "ListableBeanFactory must not be null");
+ Assert.hasLength(unitName, "Unit name must not be empty");
+
+ // See whether we can find an EntityManagerFactory with matching persistence unit name.
+ String[] candidateNames =
+ BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, EntityManagerFactory.class);
+ for (String candidateName : candidateNames) {
+ EntityManagerFactory emf = (EntityManagerFactory) beanFactory.getBean(candidateName);
+ if (emf instanceof EntityManagerFactoryInfo) {
+ if (unitName.equals(((EntityManagerFactoryInfo) emf).getPersistenceUnitName())) {
+ return emf;
+ }
+ }
+ }
+ // No matching persistence unit found - simply take the EntityManagerFactory
+ // with the persistence unit name as bean name (by convention).
+ return (EntityManagerFactory) beanFactory.getBean(unitName, EntityManagerFactory.class);
+ }
+
+ /**
+ * Obtain a JPA EntityManager from the given factory. Is aware of a
+ * corresponding EntityManager bound to the current thread,
+ * for example when using JpaTransactionManager.
+ * Note: Will return Note: Will return Same as The most important cases like object not found or optimistic locking
+ * failure are covered here. For more fine-granular conversion, JpaAccessor and
+ * JpaTransactionManager support sophisticated translation of exceptions via a
+ * JpaDialect.
+ * @param ex runtime exception that occured
+ * @return the corresponding DataAccessException instance,
+ * or Note: This is an SPI class, not intended to be used by applications.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see JpaTransactionManager
+ * @see EntityManagerFactoryUtils
+ */
+public class EntityManagerHolder extends ResourceHolderSupport {
+
+ private final EntityManager entityManager;
+
+ private boolean transactionActive;
+
+ private SavepointManager savepointManager;
+
+
+ public EntityManagerHolder(EntityManager entityManager) {
+ Assert.notNull(entityManager, "EntityManager must not be null");
+ this.entityManager = entityManager;
+ }
+
+
+ public EntityManager getEntityManager() {
+ return this.entityManager;
+ }
+
+ protected void setTransactionActive(boolean transactionActive) {
+ this.transactionActive = transactionActive;
+ }
+
+ protected boolean isTransactionActive() {
+ return this.transactionActive;
+ }
+
+ protected void setSavepointManager(SavepointManager savepointManager) {
+ this.savepointManager = savepointManager;
+ }
+
+ protected SavepointManager getSavepointManager() {
+ return this.savepointManager;
+ }
+
+ public void clear() {
+ super.clear();
+ this.transactionActive = false;
+ this.savepointManager = null;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerPlus.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerPlus.java
new file mode 100644
index 00000000000..b1c44058e98
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerPlus.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+import javax.persistence.EntityManager;
+
+/**
+ * Extension of the standard JPA EntityManager interface, linking in
+ * Spring's EntityManagerPlusOperations interface which defines additional
+ * operations (beyond JPA 1.0) in a vendor-independent fashion.
+ *
+ * @author Rod Johnson
+ * @since 2.0
+ * @see javax.persistence.EntityManager
+ */
+public interface EntityManagerPlus extends EntityManager, EntityManagerPlusOperations {
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerPlusOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerPlusOperations.java
new file mode 100644
index 00000000000..d2b835cc055
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerPlusOperations.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+/**
+ * Interface that defines common operations beyond the standard
+ * JPA EntityManager interface, in a vendor-independent fashion.
+ * To be adapted to specific JPA providers through a JpaDialect.
+ *
+ * As of Spring 2.0, this interface does not define any operations yet.
+ * The pass-through mechanism to the underlying JpaDialect is already in
+ * place. Concrete operations will be added in the Spring 2.5 timeframe.
+ *
+ * @author Rod Johnson
+ * @since 2.0
+ * @see JpaDialect#getEntityManagerPlusOperations
+ * @see javax.persistence.EntityManager
+ */
+public interface EntityManagerPlusOperations {
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerProxy.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerProxy.java
new file mode 100644
index 00000000000..575fe432ad7
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerProxy.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2007 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.jpa;
+
+import javax.persistence.EntityManager;
+
+/**
+ * Subinterface of {@link javax.persistence.EntityManager} to be implemented by
+ * EntityManager proxies. Allows access to the underlying target EntityManager.
+ *
+ * This interface is mainly intended for framework usage. Application code
+ * should prefer the use of the {@link javax.persistence.EntityManager#getDelegate()}
+ * method to access native functionality of the underlying resource.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public interface EntityManagerProxy extends EntityManager {
+
+ /**
+ * Return the underlying EntityManager that this proxy will delegate to.
+ * In case of an extended EntityManager, this will be the associated
+ * raw EntityManager.
+ * In case of a shared ("transactional") EntityManager, this will be
+ * the raw EntityManager that is currently associated with the transaction.
+ * Outside of a transaction, an IllegalStateException will be thrown.
+ * @return the underlying raw EntityManager (never Supports explicit joining of a transaction through the
+ * Not intended to be used directly.
+ * See {@link JpaTemplate} and {@link JpaInterceptor}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setEntityManagerFactory
+ * @see #setEntityManager
+ * @see #setJpaDialect
+ * @see #setFlushEager
+ * @see JpaTemplate
+ * @see JpaInterceptor
+ * @see JpaDialect
+ */
+public abstract class JpaAccessor extends EntityManagerFactoryAccessor implements InitializingBean {
+
+ private EntityManager entityManager;
+
+ private JpaDialect jpaDialect = new DefaultJpaDialect();
+
+ private boolean flushEager = false;
+
+
+ /**
+ * Set the JPA EntityManager to use.
+ */
+ public void setEntityManager(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ /**
+ * Return the JPA EntityManager to use.
+ */
+ public EntityManager getEntityManager() {
+ return entityManager;
+ }
+
+ /**
+ * Set the JPA dialect to use for this accessor.
+ * The dialect object can be used to retrieve the underlying JDBC
+ * connection, for example.
+ */
+ public void setJpaDialect(JpaDialect jpaDialect) {
+ this.jpaDialect = (jpaDialect != null ? jpaDialect : new DefaultJpaDialect());
+ }
+
+ /**
+ * Return the JPA dialect to use for this accessor.
+ * Creates a default one for the specified EntityManagerFactory if none set.
+ */
+ public JpaDialect getJpaDialect() {
+ return this.jpaDialect;
+ }
+
+ /**
+ * Set if this accessor should flush changes to the database eagerly.
+ * Eager flushing leads to immediate synchronization with the database,
+ * even if in a transaction. This causes inconsistencies to show up and throw
+ * a respective exception immediately, and JDBC access code that participates
+ * in the same transaction will see the changes as the database is already
+ * aware of them then. But the drawbacks are:
+ * Default implementation delegates to the JpaDialect.
+ * May be overridden in subclasses.
+ * @param ex runtime exception that occured, which may or may not
+ * be JPA-related
+ * @return the corresponding DataAccessException instance if
+ * wrapping should occur, otherwise the raw exception
+ * @see org.springframework.dao.support.DataAccessUtils#translateIfNecessary
+ */
+ public RuntimeException translateIfNecessary(RuntimeException ex) {
+ return DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaCallback.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaCallback.java
new file mode 100644
index 00000000000..42fef7fad5b
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaCallback.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+
+/**
+ * Callback interface for JPA code. To be used with {@link JpaTemplate}'s
+ * execution method, often as anonymous classes within a method implementation.
+ * A typical implementation will call Note that JPA callback code will not flush any modifications to the
+ * database if not executed within a transaction. Thus, you need to make
+ * sure that JpaTransactionManager has initiated a JPA transaction when
+ * the callback gets called, at least if you want to write to the database.
+ *
+ * Allows for returning a result object created within the callback,
+ * i.e. a domain object or a collection of domain objects.
+ * A thrown custom RuntimeException is treated as an application exception:
+ * It gets propagated to the caller of the template.
+ *
+ * @param em active EntityManager
+ * @return a result object, or Also allows for the provision of value-added methods for portable yet
+ * more capable EntityManager and EntityManagerFactory subinterfaces offered
+ * by Spring.
+ *
+ * In general, it is recommended to derive from DefaultJpaDialect instead of
+ * implementing this interface directly. This allows for inheriting common
+ * behavior (present and future) from DefaultJpaDialect, only overriding
+ * specific hooks to plug in concrete vendor-specific behavior.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.0
+ * @see DefaultJpaDialect
+ * @see JpaAccessor#setJpaDialect
+ * @see JpaTransactionManager#setJpaDialect
+ * @see JpaVendorAdapter#getJpaDialect()
+ * @see AbstractEntityManagerFactoryBean#setJpaDialect
+ * @see AbstractEntityManagerFactoryBean#setJpaVendorAdapter
+ */
+public interface JpaDialect extends PersistenceExceptionTranslator {
+
+ //-----------------------------------------------------------------------------------
+ // Hooks for non-standard persistence operations (used by EntityManagerFactory beans)
+ //-----------------------------------------------------------------------------------
+
+ /**
+ * Return whether the EntityManagerFactoryPlus(Operations) interface is
+ * supported by this provider.
+ * @see EntityManagerFactoryPlusOperations
+ * @see EntityManagerFactoryPlus
+ */
+ boolean supportsEntityManagerFactoryPlusOperations();
+
+ /**
+ * Return whether the EntityManagerPlus(Operations) interface is
+ * supported by this provider.
+ * @see EntityManagerPlusOperations
+ * @see EntityManagerPlus
+ */
+ boolean supportsEntityManagerPlusOperations();
+
+ /**
+ * Return an EntityManagerFactoryPlusOperations implementation for
+ * the given raw EntityManagerFactory. This operations object can be
+ * used to serve the additional operations behind a proxy that
+ * implements the EntityManagerFactoryPlus interface.
+ * @param rawEntityManager the raw provider-specific EntityManagerFactory
+ * @return the EntityManagerFactoryPlusOperations implementation
+ */
+ EntityManagerFactoryPlusOperations getEntityManagerFactoryPlusOperations(EntityManagerFactory rawEntityManager);
+
+ /**
+ * Return an EntityManagerPlusOperations implementation for
+ * the given raw EntityManager. This operations object can be
+ * used to serve the additional operations behind a proxy that
+ * implements the EntityManagerPlus interface.
+ * @param rawEntityManager the raw provider-specific EntityManagerFactory
+ * @return the EntityManagerFactoryPlusOperations implementation
+ */
+ EntityManagerPlusOperations getEntityManagerPlusOperations(EntityManager rawEntityManager);
+
+
+ //-------------------------------------------------------------------------
+ // Hooks for transaction management (used by JpaTransactionManager)
+ //-------------------------------------------------------------------------
+
+ /**
+ * Begin the given JPA transaction, applying the semantics specified by the
+ * given Spring transaction definition (in particular, an isolation level
+ * and a timeout). Called by JpaTransactionManager on transaction begin.
+ * An implementation can configure the JPA Transaction object and then
+ * invoke An implementation can apply the read-only flag as flush mode. In that case,
+ * a transaction data object can be returned that holds the previous flush mode
+ * (and possibly other data), to be reset in Implementations can also use the Spring transaction name, as exposed by the
+ * passed-in TransactionDefinition, to optimize for specific data access use cases
+ * (effectively using the current transaction name as use case identifier).
+ * This method also allows for exposing savepoint capabilities if supported by
+ * the persistence provider, through returning an Object that implements Spring's
+ * {@link org.springframework.transaction.SavepointManager} interface.
+ * {@link JpaTransactionManager} will use this capability if needed.
+ * @param entityManager the EntityManager to begin a JPA transaction on
+ * @param definition the Spring transaction definition that defines semantics
+ * @return an arbitrary object that holds transaction data, if any
+ * (to be passed into {@link #cleanupTransaction}). May implement the
+ * {@link org.springframework.transaction.SavepointManager} interface.
+ * @throws javax.persistence.PersistenceException if thrown by JPA methods
+ * @throws java.sql.SQLException if thrown by JDBC methods
+ * @throws org.springframework.transaction.TransactionException in case of invalid arguments
+ * @see #cleanupTransaction
+ * @see javax.persistence.EntityTransaction#begin
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#prepareConnectionForTransaction
+ */
+ Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
+ throws PersistenceException, SQLException, TransactionException;
+
+ /**
+ * Prepare a JPA transaction, applying the specified semantics. Called by
+ * EntityManagerFactoryUtils when enlisting an EntityManager in a JTA transaction.
+ * An implementation can apply the read-only flag as flush mode. In that case,
+ * a transaction data object can be returned that holds the previous flush mode
+ * (and possibly other data), to be reset in Implementations can also use the Spring transaction name, as exposed by the
+ * passed-in TransactionDefinition, to optimize for specific data access use cases
+ * (effectively using the current transaction name as use case identifier).
+ * @param entityManager the EntityManager to begin a JPA transaction on
+ * @param readOnly whether the transaction is supposed to be read-only
+ * @param name the name of the transaction (if any)
+ * @return an arbitrary object that holds transaction data, if any
+ * (to be passed into cleanupTransaction)
+ * @throws javax.persistence.PersistenceException if thrown by JPA methods
+ * @see #cleanupTransaction
+ */
+ Object prepareTransaction(EntityManager entityManager, boolean readOnly, String name)
+ throws PersistenceException;
+
+ /**
+ * Clean up the transaction via the given transaction data. Called by
+ * JpaTransactionManager and EntityManagerFactoryUtils on transaction cleanup.
+ * An implementation can, for example, reset read-only flag and
+ * isolation level of the underlying JDBC Connection. Furthermore,
+ * an exposed data access use case can be reset here.
+ * @param transactionData arbitrary object that holds transaction data, if any
+ * (as returned by beginTransaction or prepareTransaction)
+ * @see #beginTransaction
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#resetConnectionAfterTransaction
+ */
+ void cleanupTransaction(Object transactionData);
+
+ /**
+ * Retrieve the JDBC Connection that the given JPA EntityManager uses underneath,
+ * if accessing a relational database. This method will just get invoked if actually
+ * needing access to the underlying JDBC Connection, usually within an active JPA
+ * transaction (for example, by JpaTransactionManager). The returned handle will
+ * be passed into the This strategy is necessary as JPA 1.0 does not provide a standard way to retrieve
+ * the underlying JDBC Connection (due to the fact that a JPA implementation might not
+ * work with a relational database at all).
+ * Implementations are encouraged to return an unwrapped Connection object, i.e.
+ * the Connection as they got it from the connection pool. This makes it easier for
+ * application code to get at the underlying native JDBC Connection, like an
+ * OracleConnection, which is sometimes necessary for LOB handling etc. We assume
+ * that calling code knows how to properly handle the returned Connection object.
+ * In a simple case where the returned Connection will be auto-closed with the
+ * EntityManager or can be released via the Connection object itself, an
+ * implementation can return a SimpleConnectionHandle that just contains the
+ * Connection. If some other object is needed in An implementation might simply do nothing, if the Connection returned
+ * by Application code must retrieve a JPA EntityManager via the
+ * Note that this interceptor automatically translates PersistenceExceptions,
+ * via delegating to the This class can be considered a declarative alternative to JpaTemplate's
+ * callback approach. The advantages are:
+ * The drawback is the dependency on interceptor configuration. However, note
+ * that this interceptor is usually not necessary in scenarios where the
+ * data access code always executes within transactions. A transaction will always
+ * have a thread-bound EntityManager in the first place, so adding this interceptor
+ * to the configuration just adds value when fine-tuning EntityManager settings
+ * like the flush mode - or when relying on exception translation.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see JpaTransactionManager
+ * @see JpaTemplate
+ */
+public class JpaInterceptor extends JpaAccessor implements MethodInterceptor {
+
+ private boolean exceptionConversionEnabled = true;
+
+
+ /**
+ * Set whether to convert any PersistenceException raised to a Spring DataAccessException,
+ * compatible with the Default is "true". Turn this flag off to let the caller receive raw exceptions
+ * as-is, without any wrapping.
+ * @see org.springframework.dao.DataAccessException
+ */
+ public void setExceptionConversionEnabled(boolean exceptionConversionEnabled) {
+ this.exceptionConversionEnabled = exceptionConversionEnabled;
+ }
+
+
+ public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+ // Determine current EntityManager: either the transactional one
+ // managed by the factory or a temporary one for the given invocation.
+ EntityManager em = getTransactionalEntityManager();
+ boolean isNewEm = false;
+ if (em == null) {
+ logger.debug("Creating new EntityManager for JpaInterceptor invocation");
+ em = createEntityManager();
+ isNewEm = true;
+ TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), new EntityManagerHolder(em));
+ }
+
+ try {
+ Object retVal = methodInvocation.proceed();
+ flushIfNecessary(em, !isNewEm);
+ return retVal;
+ }
+ catch (RuntimeException rawException) {
+ if (this.exceptionConversionEnabled) {
+ // Translation enabled. Translate if we understand the exception.
+ throw translateIfNecessary(rawException);
+ }
+ else {
+ // Translation not enabled. Don't try to translate.
+ throw rawException;
+ }
+ }
+ finally {
+ if (isNewEm) {
+ TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
+ EntityManagerFactoryUtils.closeEntityManager(em);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaObjectRetrievalFailureException.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaObjectRetrievalFailureException.java
new file mode 100644
index 00000000000..ef5a04f151e
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaObjectRetrievalFailureException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+import javax.persistence.EntityNotFoundException;
+
+import org.springframework.orm.ObjectRetrievalFailureException;
+
+/**
+ * JPA-specific subclass of ObjectRetrievalFailureException.
+ * Converts JPA's EntityNotFoundException.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
+ */
+public class JpaObjectRetrievalFailureException extends ObjectRetrievalFailureException {
+
+ public JpaObjectRetrievalFailureException(EntityNotFoundException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaOperations.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaOperations.java
new file mode 100644
index 00000000000..3e7b8ae138f
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaOperations.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2006 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.jpa;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Interface that specifies a basic set of JPA operations,
+ * implemented by {@link JpaTemplate}. Not often used, but a useful
+ * option to enhance testability, as it can easily be mocked or stubbed.
+ *
+ * Defines Note that lazy loading will just work with an open JPA
+ * The central method is of this template is "execute", supporting JPA access code
+ * implementing the {@link JpaCallback} interface. It provides JPA EntityManager
+ * handling such that neither the JpaCallback implementation nor the calling code
+ * needs to explicitly care about retrieving/closing EntityManagers, or handling
+ * JPA lifecycle exceptions.
+ *
+ * Can be used within a service implementation via direct instantiation with
+ * a EntityManagerFactory reference, or get prepared in an application context
+ * and given to services as bean reference. Note: The EntityManagerFactory should
+ * always be configured as bean in the application context, in the first case
+ * given to the service directly, in the second case to the prepared template.
+ *
+ * NOTE: JpaTemplate mainly exists as a sibling of JdoTemplate and
+ * HibernateTemplate, offering the same style for people used to it. For newly
+ * started projects, consider adopting the standard JPA style of coding data
+ * access objects instead, based on a "shared EntityManager" reference injected
+ * via a Spring bean definition or the JPA PersistenceContext annotation.
+ * (Using Spring's SharedEntityManagerBean / PersistenceAnnotationBeanPostProcessor,
+ * or using a direct JNDI lookup for an EntityManager on a Java EE 5 server.)
+ *
+ * JpaTemplate can be considered as direct alternative to working with the
+ * native JPA EntityManager API (through a shared EntityManager reference,
+ * as outlined above). The major advantage is its automatic conversion to
+ * DataAccessExceptions; the major disadvantage is that it introduces
+ * another thin layer on top of the native JPA API. Note that exception
+ * translation can also be achieved through AOP advice; check out
+ * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}.
+ *
+ * {@link LocalContainerEntityManagerFactoryBean} is the preferred way of
+ * obtaining a reference to an EntityManagerFactory, at least outside of a full
+ * Java EE 5 environment. The Spring application context will manage its lifecycle,
+ * initializing and shutting down the factory as part of the application.
+ * Within a Java EE 5 environment, you will typically work with a server-managed
+ * EntityManagerFactory that is exposed via JNDI, obtained through Spring's
+ * {@link org.springframework.jndi.JndiObjectFactoryBean}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setEntityManagerFactory
+ * @see JpaCallback
+ * @see javax.persistence.EntityManager
+ * @see LocalEntityManagerFactoryBean
+ * @see LocalContainerEntityManagerFactoryBean
+ * @see JpaTransactionManager
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
+ * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
+ */
+public class JpaTemplate extends JpaAccessor implements JpaOperations {
+
+ private boolean exposeNativeEntityManager = false;
+
+
+ /**
+ * Create a new JpaTemplate instance.
+ */
+ public JpaTemplate() {
+ }
+
+ /**
+ * Create a new JpaTemplate instance.
+ * @param emf EntityManagerFactory to create EntityManagers
+ */
+ public JpaTemplate(EntityManagerFactory emf) {
+ setEntityManagerFactory(emf);
+ afterPropertiesSet();
+ }
+
+ /**
+ * Create a new JpaTemplate instance.
+ * @param em EntityManager to use
+ */
+ public JpaTemplate(EntityManager em) {
+ setEntityManager(em);
+ afterPropertiesSet();
+ }
+
+
+ /**
+ * Set whether to expose the native JPA EntityManager to JpaCallback
+ * code. Default is "false": a EntityManager proxy will be returned,
+ * suppressing As there is often a need to cast to a provider-specific EntityManager
+ * class in DAOs that use the JPA 1.0 API, for JPA 2.0 previews and other
+ * provider-specific functionality, the exposed proxy implements all interfaces
+ * implemented by the original EntityManager. If this is not sufficient,
+ * turn this flag to "true".
+ * @see JpaCallback
+ * @see javax.persistence.EntityManager
+ */
+ public void setExposeNativeEntityManager(boolean exposeNativeEntityManager) {
+ this.exposeNativeEntityManager = exposeNativeEntityManager;
+ }
+
+ /**
+ * Return whether to expose the native JPA EntityManager to JpaCallback
+ * code, or rather an EntityManager proxy.
+ */
+ public boolean isExposeNativeEntityManager() {
+ return this.exposeNativeEntityManager;
+ }
+
+
+ public Object execute(JpaCallback action) throws DataAccessException {
+ return execute(action, isExposeNativeEntityManager());
+ }
+
+ public List executeFind(JpaCallback action) throws DataAccessException {
+ Object result = execute(action, isExposeNativeEntityManager());
+ if (!(result instanceof List)) {
+ throw new InvalidDataAccessApiUsageException(
+ "Result object returned from JpaCallback isn't a List: [" + result + "]");
+ }
+ return (List) result;
+ }
+
+ /**
+ * Execute the action specified by the given action object within a
+ * EntityManager.
+ * @param action callback object that specifies the JPA action
+ * @param exposeNativeEntityManager whether to expose the native
+ * JPA entity manager to callback code
+ * @return a result object returned by the action, or This transaction manager is appropriate for applications that use a single
+ * JPA EntityManagerFactory for transactional data access. JTA (usually through
+ * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary
+ * for accessing multiple transactional resources within the same transaction.
+ * Note that you need to configure your JPA provider accordingly in order to make
+ * it participate in JTA transactions.
+ *
+ * This transaction manager 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 JPA and services which use plain
+ * JDBC (without being aware of JPA)! Application code needs to stick to the
+ * same simple Connection lookup pattern as with
+ * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
+ * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
+ * or going through a
+ * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
+ * Note that this requires a vendor-specific {@link JpaDialect} to be configured.
+ *
+ * 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
+ * EntityManagerFactory. This transaction manager will autodetect the DataSource
+ * used as known connection factory of the EntityManagerFactory, so you usually
+ * don't need to explicitly specify the "dataSource" property.
+ *
+ * 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 JPA EntityManager 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 JPA transactions (provided that your
+ * JDBC driver supports Savepoints). Note that JPA itself does not support
+ * nested transactions! Hence, do not expect JPA access code to semantically
+ * participate in a nested transaction.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setEntityManagerFactory
+ * @see #setDataSource
+ * @see LocalEntityManagerFactoryBean
+ * @see JpaTemplate#execute
+ * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
+ * @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
+ */
+public class JpaTransactionManager extends AbstractPlatformTransactionManager
+ implements ResourceTransactionManager, InitializingBean {
+
+ private EntityManagerFactory entityManagerFactory;
+
+ private final Map jpaPropertyMap = new HashMap();
+
+ private DataSource dataSource;
+
+ private JpaDialect jpaDialect = new DefaultJpaDialect();
+
+
+ /**
+ * Create a new JpaTransactionManager instance.
+ * A EntityManagerFactory has to be set to be able to use it.
+ * @see #setEntityManagerFactory
+ */
+ public JpaTransactionManager() {
+ setNestedTransactionAllowed(true);
+ }
+
+ /**
+ * Create a new JpaTransactionManager instance.
+ * @param emf EntityManagerFactory to manage transactions for
+ */
+ public JpaTransactionManager(EntityManagerFactory emf) {
+ this();
+ this.entityManagerFactory = emf;
+ afterPropertiesSet();
+ }
+
+
+ /**
+ * Set the EntityManagerFactory that this instance should manage transactions for.
+ */
+ public void setEntityManagerFactory(EntityManagerFactory emf) {
+ this.entityManagerFactory = emf;
+ }
+
+ /**
+ * Return the EntityManagerFactory that this instance should manage transactions for.
+ */
+ public EntityManagerFactory getEntityManagerFactory() {
+ return this.entityManagerFactory;
+ }
+
+ /**
+ * Specify JPA properties, to be passed into
+ * Can be populated with a String "value" (parsed via PropertiesEditor)
+ * or a "props" element in XML bean definitions.
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ public void setJpaProperties(Properties jpaProperties) {
+ CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
+ }
+
+ /**
+ * Specify JPA properties as a Map, to be passed into
+ * Can be populated with a "map" or "props" element in XML bean definitions.
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ public void setJpaPropertyMap(Map jpaProperties) {
+ if (jpaProperties != null) {
+ this.jpaPropertyMap.putAll(jpaProperties);
+ }
+ }
+
+ /**
+ * Allow Map access to the JPA properties to be passed to the persistence
+ * provider, with the option to add or override specific entries.
+ * Useful for specifying entries directly, for example via "jpaPropertyMap[myKey]".
+ */
+ public Map getJpaPropertyMap() {
+ return this.jpaPropertyMap;
+ }
+
+ /**
+ * Set the JDBC DataSource that this instance should manage transactions for.
+ * The DataSource should match the one used by the JPA EntityManagerFactory:
+ * for example, you could specify the same JNDI DataSource for both.
+ * If the EntityManagerFactory uses a known DataSource as connection factory,
+ * the DataSource will be autodetected: You can still explictly specify the
+ * DataSource, but you don't need to in this case.
+ * A transactional JDBC Connection for this DataSource will be provided to
+ * application code accessing this DataSource directly via DataSourceUtils
+ * or JdbcTemplate. The Connection will be taken from the JPA EntityManager.
+ * Note that you need to use a JPA dialect for a specific JPA implementation
+ * to allow for exposing JPA transactions as JDBC transactions.
+ * 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 EntityManagerFactoryInfo#getDataSource()
+ * @see #setJpaDialect
+ * @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 the JPA dialect to use for this transaction manager.
+ * Used for vendor-specific transaction management and JDBC connection exposure.
+ * If the EntityManagerFactory uses a known JpaDialect, it will be autodetected:
+ * You can still explictly specify the DataSource, but you don't need to in this case.
+ * The dialect object can be used to retrieve the underlying JDBC connection
+ * and thus allows for exposing JPA transactions as JDBC transactions.
+ * @see EntityManagerFactoryInfo#getJpaDialect()
+ * @see JpaDialect#beginTransaction
+ * @see JpaDialect#getJdbcConnection
+ */
+ public void setJpaDialect(JpaDialect jpaDialect) {
+ this.jpaDialect = (jpaDialect != null ? jpaDialect : new DefaultJpaDialect());
+ }
+
+ /**
+ * Return the JPA dialect to use for this transaction manager.
+ */
+ public JpaDialect getJpaDialect() {
+ return this.jpaDialect;
+ }
+
+ /**
+ * Eagerly initialize the JPA dialect, creating a default one
+ * for the specified EntityManagerFactory if none set.
+ * Auto-detect the EntityManagerFactory's DataSource, if any.
+ */
+ public void afterPropertiesSet() {
+ if (getEntityManagerFactory() == null) {
+ throw new IllegalArgumentException("Property 'entityManagerFactory' is required");
+ }
+ if (getEntityManagerFactory() instanceof EntityManagerFactoryInfo) {
+ EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) getEntityManagerFactory();
+ DataSource dataSource = emfInfo.getDataSource();
+ if (dataSource != null) {
+ setDataSource(dataSource);
+ }
+ JpaDialect jpaDialect = emfInfo.getJpaDialect();
+ if (jpaDialect != null) {
+ setJpaDialect(jpaDialect);
+ }
+ }
+ }
+
+
+ public Object getResourceFactory() {
+ return getEntityManagerFactory();
+ }
+
+ protected Object doGetTransaction() {
+ JpaTransactionObject txObject = new JpaTransactionObject();
+ txObject.setSavepointAllowed(isNestedTransactionAllowed());
+
+ EntityManagerHolder emHolder = (EntityManagerHolder)
+ TransactionSynchronizationManager.getResource(getEntityManagerFactory());
+ if (emHolder != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found thread-bound EntityManager [" +
+ emHolder.getEntityManager() + "] for JPA transaction");
+ }
+ txObject.setEntityManagerHolder(emHolder, false);
+ }
+
+ if (getDataSource() != null) {
+ ConnectionHolder conHolder = (ConnectionHolder)
+ TransactionSynchronizationManager.getResource(getDataSource());
+ txObject.setConnectionHolder(conHolder);
+ }
+
+ return txObject;
+ }
+
+ protected boolean isExistingTransaction(Object transaction) {
+ return ((JpaTransactionObject) transaction).hasTransaction();
+ }
+
+ protected void doBegin(Object transaction, TransactionDefinition definition) {
+ JpaTransactionObject txObject = (JpaTransactionObject) transaction;
+
+ if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
+ throw new IllegalTransactionStateException(
+ "Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
+ "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
+ "It is recommended to use a single JpaTransactionManager for all transactions " +
+ "on a single DataSource, no matter whether JPA or JDBC access.");
+ }
+
+ EntityManager em = null;
+
+ try {
+ if (txObject.getEntityManagerHolder() == null ||
+ txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
+ EntityManager newEm = createEntityManagerForTransaction();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
+ }
+ txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
+ }
+
+ em = txObject.getEntityManagerHolder().getEntityManager();
+
+ // Delegate to JpaDialect for actual transaction begin.
+ Object transactionData = getJpaDialect().beginTransaction(em, definition);
+ txObject.setTransactionData(transactionData);
+
+ // Register transaction timeout.
+ int timeout = determineTimeout(definition);
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ txObject.getEntityManagerHolder().setTimeoutInSeconds(timeout);
+ }
+
+ // Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
+ if (getDataSource() != null) {
+ ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
+ if (conHandle != null) {
+ ConnectionHolder conHolder = new ConnectionHolder(conHandle);
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ conHolder.setTimeoutInSeconds(timeout);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exposing JPA transaction as JDBC transaction [" + conHolder.getConnectionHandle() + "]");
+ }
+ TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
+ txObject.setConnectionHolder(conHolder);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because JpaDialect [" +
+ getJpaDialect() + "] does not support JDBC Connection retrieval");
+ }
+ }
+ }
+
+ // Bind the entity manager holder to the thread.
+ if (txObject.isNewEntityManagerHolder()) {
+ TransactionSynchronizationManager.bindResource(
+ getEntityManagerFactory(), txObject.getEntityManagerHolder());
+ }
+ txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
+ }
+
+ catch (TransactionException ex) {
+ closeEntityManagerAfterFailedBegin(txObject);
+ throw ex;
+ }
+ catch (Exception ex) {
+ closeEntityManagerAfterFailedBegin(txObject);
+ throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
+ }
+ }
+
+ /**
+ * Create a JPA EntityManager to be used for a transaction.
+ * The default implementation checks whether the EntityManagerFactory
+ * is a Spring proxy and unwraps it first.
+ * @see javax.persistence.EntityManagerFactory#createEntityManager()
+ * @see EntityManagerFactoryInfo#getNativeEntityManagerFactory()
+ */
+ protected EntityManager createEntityManagerForTransaction() {
+ EntityManagerFactory emf = getEntityManagerFactory();
+ if (emf instanceof EntityManagerFactoryInfo) {
+ emf = ((EntityManagerFactoryInfo) emf).getNativeEntityManagerFactory();
+ }
+ Map properties = getJpaPropertyMap();
+ return (!CollectionUtils.isEmpty(properties) ?
+ emf.createEntityManager(properties) : emf.createEntityManager());
+ }
+
+ /**
+ * Close the current transaction's EntityManager.
+ * Called after a transaction begin attempt failed.
+ * @param txObject the current transaction
+ */
+ protected void closeEntityManagerAfterFailedBegin(JpaTransactionObject txObject) {
+ if (txObject.isNewEntityManagerHolder()) {
+ EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
+ try {
+ if (em.getTransaction().isActive()) {
+ em.getTransaction().rollback();
+ }
+ }
+ catch (Throwable ex) {
+ logger.debug("Could not rollback EntityManager after failed transaction begin", ex);
+ }
+ finally {
+ EntityManagerFactoryUtils.closeEntityManager(em);
+ }
+ }
+ }
+
+ protected Object doSuspend(Object transaction) {
+ JpaTransactionObject txObject = (JpaTransactionObject) transaction;
+ txObject.setEntityManagerHolder(null, false);
+ EntityManagerHolder entityManagerHolder = (EntityManagerHolder)
+ TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
+ txObject.setConnectionHolder(null);
+ ConnectionHolder connectionHolder = null;
+ if (getDataSource() != null && TransactionSynchronizationManager.hasResource(getDataSource())) {
+ connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
+ }
+ return new SuspendedResourcesHolder(entityManagerHolder, connectionHolder);
+ }
+
+ protected void doResume(Object transaction, Object suspendedResources) {
+ SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
+ TransactionSynchronizationManager.bindResource(
+ getEntityManagerFactory(), resourcesHolder.getEntityManagerHolder());
+ if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) {
+ TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
+ }
+ }
+
+ /**
+ * This implementation returns "true": a JPA commit will properly handle
+ * transactions that have been marked rollback-only at a global level.
+ */
+ protected boolean shouldCommitOnGlobalRollbackOnly() {
+ return true;
+ }
+
+ protected void doCommit(DefaultTransactionStatus status) {
+ JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Committing JPA transaction on EntityManager [" +
+ txObject.getEntityManagerHolder().getEntityManager() + "]");
+ }
+ try {
+ EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
+ tx.commit();
+ }
+ catch (RollbackException ex) {
+ if (ex.getCause() instanceof RuntimeException) {
+ DataAccessException dex = getJpaDialect().translateExceptionIfPossible((RuntimeException) ex.getCause());
+ if (dex != null) {
+ throw dex;
+ }
+ }
+ throw new TransactionSystemException("Could not commit JPA transaction", ex);
+ }
+ catch (RuntimeException ex) {
+ // Assumably failed to flush changes to database.
+ throw DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
+ }
+ }
+
+ protected void doRollback(DefaultTransactionStatus status) {
+ JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Rolling back JPA transaction on EntityManager [" +
+ txObject.getEntityManagerHolder().getEntityManager() + "]");
+ }
+ try {
+ EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
+ if (tx.isActive()) {
+ tx.rollback();
+ }
+ }
+ catch (PersistenceException ex) {
+ throw new TransactionSystemException("Could not roll back JPA transaction", ex);
+ }
+ finally {
+ if (!txObject.isNewEntityManagerHolder()) {
+ // Clear all pending inserts/updates/deletes in the EntityManager.
+ // Necessary for pre-bound EntityManagers, to avoid inconsistent state.
+ txObject.getEntityManagerHolder().getEntityManager().clear();
+ }
+ }
+ }
+
+ protected void doSetRollbackOnly(DefaultTransactionStatus status) {
+ JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Setting JPA transaction on EntityManager [" +
+ txObject.getEntityManagerHolder().getEntityManager() + "] rollback-only");
+ }
+ txObject.setRollbackOnly();
+ }
+
+ protected void doCleanupAfterCompletion(Object transaction) {
+ JpaTransactionObject txObject = (JpaTransactionObject) transaction;
+
+ // Remove the entity manager holder from the thread.
+ if (txObject.isNewEntityManagerHolder()) {
+ TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
+ }
+ txObject.getEntityManagerHolder().clear();
+
+ // Remove the JDBC connection holder from the thread, if exposed.
+ if (txObject.hasConnectionHolder()) {
+ TransactionSynchronizationManager.unbindResource(getDataSource());
+ try {
+ getJpaDialect().releaseJdbcConnection(txObject.getConnectionHolder().getConnectionHandle(),
+ txObject.getEntityManagerHolder().getEntityManager());
+ }
+ catch (Exception ex) {
+ // Just log it, to keep a transaction-related exception.
+ logger.error("Could not close JDBC connection after transaction", ex);
+ }
+ }
+
+ getJpaDialect().cleanupTransaction(txObject.getTransactionData());
+
+ // Remove the entity manager holder from the thread.
+ if (txObject.isNewEntityManagerHolder()) {
+ EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
+ }
+ EntityManagerFactoryUtils.closeEntityManager(em);
+ }
+ else {
+ logger.debug("Not closing pre-bound JPA EntityManager after transaction");
+ }
+ }
+
+
+ /**
+ * JPA transaction object, representing a EntityManagerHolder.
+ * Used as transaction object by JpaTransactionManager.
+ */
+ private static class JpaTransactionObject extends JdbcTransactionObjectSupport {
+
+ private EntityManagerHolder entityManagerHolder;
+
+ private boolean newEntityManagerHolder;
+
+ private Object transactionData;
+
+ public void setEntityManagerHolder(
+ EntityManagerHolder entityManagerHolder, boolean newEntityManagerHolder) {
+ this.entityManagerHolder = entityManagerHolder;
+ this.newEntityManagerHolder = newEntityManagerHolder;
+ }
+
+ public EntityManagerHolder getEntityManagerHolder() {
+ return this.entityManagerHolder;
+ }
+
+ public boolean isNewEntityManagerHolder() {
+ return this.newEntityManagerHolder;
+ }
+
+ public boolean hasTransaction() {
+ return (this.entityManagerHolder != null && this.entityManagerHolder.isTransactionActive());
+ }
+
+ public void setTransactionData(Object transactionData) {
+ this.transactionData = transactionData;
+ this.entityManagerHolder.setTransactionActive(true);
+ if (transactionData instanceof SavepointManager) {
+ this.entityManagerHolder.setSavepointManager((SavepointManager) transactionData);
+ }
+ }
+
+ public Object getTransactionData() {
+ return this.transactionData;
+ }
+
+ public void setRollbackOnly() {
+ EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction();
+ if (tx.isActive()) {
+ tx.setRollbackOnly();
+ }
+ if (hasConnectionHolder()) {
+ getConnectionHolder().setRollbackOnly();
+ }
+ }
+
+ public boolean isRollbackOnly() {
+ EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction();
+ return tx.getRollbackOnly();
+ }
+
+ public Object createSavepoint() throws TransactionException {
+ return getSavepointManager().createSavepoint();
+ }
+
+ public void rollbackToSavepoint(Object savepoint) throws TransactionException {
+ getSavepointManager().rollbackToSavepoint(savepoint);
+ }
+
+ public void releaseSavepoint(Object savepoint) throws TransactionException {
+ getSavepointManager().releaseSavepoint(savepoint);
+ }
+
+ private SavepointManager getSavepointManager() {
+ if (!isSavepointAllowed()) {
+ throw new NestedTransactionNotSupportedException(
+ "Transaction manager does not allow nested transactions");
+ }
+ SavepointManager savepointManager = getEntityManagerHolder().getSavepointManager();
+ if (savepointManager == null) {
+ throw new NestedTransactionNotSupportedException(
+ "JpaDialect does not support savepoints - check your JPA provider's capabilities");
+ }
+ return savepointManager;
+ }
+ }
+
+
+ /**
+ * Holder for suspended resources.
+ * Used internally by Note that there might be further JPA properties defined on
+ * the EntityManagerFactory bean, which might potentially override
+ * individual JPA property values specified here.
+ * @return a Map of JPA properties, as as accepted by the standard
+ * JPA bootstrap facilities, or If the provider does not offer any EntityManagerFactory extensions,
+ * the adapter should simply return the standard
+ * {@link javax.persistence.EntityManagerFactory} class here.
+ * @since 2.5.2
+ */
+ Class extends EntityManagerFactory> getEntityManagerFactoryInterface();
+
+ /**
+ * Return the vendor-specific EntityManager interface
+ * that this provider's EntityManagers will implement.
+ * If the provider does not offer any EntityManager extensions,
+ * the adapter should simply return the standard
+ * {@link javax.persistence.EntityManager} class here.
+ */
+ Class extends EntityManager> getEntityManagerInterface();
+
+ /**
+ * Optional callback for post-processing the native EntityManagerFactory
+ * before active use.
+ * This can be used for triggering vendor-specific initialization processes.
+ * While this is not expected to be used for most providers, it is included
+ * here as a general extension hook.
+ */
+ void postProcessEntityManagerFactory(EntityManagerFactory emf);
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
new file mode 100644
index 00000000000..5e5e9f4fb45
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+import javax.persistence.spi.PersistenceProvider;
+import javax.persistence.spi.PersistenceUnitInfo;
+import javax.sql.DataSource;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.context.weaving.LoadTimeWeaverAware;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.instrument.classloading.LoadTimeWeaver;
+import org.springframework.jdbc.datasource.lookup.SingleDataSourceLookup;
+import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
+import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
+import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
+import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
+import org.springframework.util.ClassUtils;
+
+/**
+ * {@link org.springframework.beans.factory.FactoryBean} that creates a JPA
+ * {@link javax.persistence.EntityManagerFactory} according to JPA's standard
+ * container bootstrap contract. This is the most powerful way to set
+ * up a shared JPA EntityManagerFactory in a Spring application context;
+ * the EntityManagerFactory can then be passed to JPA-based DAOs via
+ * dependency injection. Note that switching to a JNDI lookup or to a
+ * {@link org.springframework.orm.jpa.LocalEntityManagerFactoryBean}
+ * definition is just a matter of configuration!
+ *
+ * As with {@link LocalEntityManagerFactoryBean}, configuration settings
+ * are usually read in from a Internally, this FactoryBean parses the The exposed EntityManagerFactory object will implement all the interfaces of
+ * the underlying native EntityManagerFactory returned by the PersistenceProvider,
+ * plus the {@link EntityManagerFactoryInfo} interface which exposes additional
+ * metadata as assembled by this FactoryBean.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.0
+ * @see #setPersistenceXmlLocation
+ * @see #setJpaProperties
+ * @see #setJpaVendorAdapter
+ * @see #setLoadTimeWeaver
+ * @see #setDataSource
+ * @see EntityManagerFactoryInfo
+ * @see LocalEntityManagerFactoryBean
+ * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
+ * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory
+ */
+public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean
+ implements ResourceLoaderAware, LoadTimeWeaverAware {
+
+ private PersistenceUnitManager persistenceUnitManager;
+
+ private final DefaultPersistenceUnitManager internalPersistenceUnitManager =
+ new DefaultPersistenceUnitManager();
+
+ private PersistenceUnitInfo persistenceUnitInfo;
+
+
+ /**
+ * Set the PersistenceUnitManager to use for obtaining the JPA persistence unit
+ * that this FactoryBean is supposed to build an EntityManagerFactory for.
+ * The default is to rely on the local settings specified on this FactoryBean,
+ * such as "persistenceXmlLocation", "dataSource" and "loadTimeWeaver".
+ * For reuse of existing persistence unit configuration or more advanced forms
+ * of custom persistence unit handling, consider defining a separate
+ * PersistenceUnitManager bean (typically a DefaultPersistenceUnitManager instance)
+ * and linking it in here. Default is "classpath:META-INF/persistence.xml".
+ * NOTE: Only applied if no external PersistenceUnitManager specified.
+ * @param persistenceXmlLocation a Spring resource String
+ * identifying the location of the In JPA speak, a DataSource passed in here will be used as "nonJtaDataSource"
+ * on the PersistenceUnitInfo passed to the PersistenceProvider, overriding
+ * data source configuration in NOTE: Only applied if no external PersistenceUnitManager specified.
+ * @see javax.persistence.spi.PersistenceUnitInfo#getNonJtaDataSource()
+ * @see #setPersistenceUnitManager
+ */
+ public void setDataSource(DataSource dataSource) {
+ this.internalPersistenceUnitManager.setDataSourceLookup(new SingleDataSourceLookup(dataSource));
+ this.internalPersistenceUnitManager.setDefaultDataSource(dataSource);
+ }
+
+ /**
+ * Set the PersistenceUnitPostProcessors to be applied to the
+ * PersistenceUnitInfo used for creating this EntityManagerFactory.
+ * Such post-processors can, for example, register further entity
+ * classes and jar files, in addition to the metadata read in from
+ * NOTE: Only applied if no external PersistenceUnitManager specified.
+ * @see #setPersistenceUnitManager
+ */
+ public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor[] postProcessors) {
+ this.internalPersistenceUnitManager.setPersistenceUnitPostProcessors(postProcessors);
+ }
+
+ /**
+ * Specify the Spring LoadTimeWeaver to use for class instrumentation according
+ * to the JPA class transformer contract.
+ * It is a not required to specify a LoadTimeWeaver: Most providers will be
+ * able to provide a subset of their functionality without class instrumentation
+ * as well, or operate with their VM agent specified on JVM startup.
+ * In terms of Spring-provided weaving options, the most important ones are
+ * InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general)
+ * VM agent specified on JVM startup, and ReflectiveLoadTimeWeaver, which interacts
+ * with an underlying ClassLoader based on specific extended methods being available
+ * on it (for example, interacting with Spring's TomcatInstrumentableClassLoader).
+ * NOTE: As of Spring 2.5, the context's default LoadTimeWeaver (defined
+ * as bean with name "loadTimeWeaver") will be picked up automatically, if available,
+ * removing the need for LoadTimeWeaver configuration on each affected target bean.
+ * Consider using the NOTE: Only applied if no external PersistenceUnitManager specified.
+ * Otherwise, the external {@link #setPersistenceUnitManager PersistenceUnitManager}
+ * is responsible for the weaving configuration.
+ * @see org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver
+ * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
+ * @see org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader
+ */
+ public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
+ this.internalPersistenceUnitManager.setLoadTimeWeaver(loadTimeWeaver);
+ }
+
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.internalPersistenceUnitManager.setResourceLoader(resourceLoader);
+ }
+
+
+ @Override
+ protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {
+ PersistenceUnitManager managerToUse = this.persistenceUnitManager;
+ if (this.persistenceUnitManager == null) {
+ this.internalPersistenceUnitManager.afterPropertiesSet();
+ managerToUse = this.internalPersistenceUnitManager;
+ }
+
+ this.persistenceUnitInfo = determinePersistenceUnitInfo(managerToUse);
+ JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter();
+ if (jpaVendorAdapter != null && this.persistenceUnitInfo instanceof MutablePersistenceUnitInfo) {
+ ((MutablePersistenceUnitInfo) this.persistenceUnitInfo).setPersistenceProviderPackageName(
+ jpaVendorAdapter.getPersistenceProviderRootPackage());
+ }
+
+ PersistenceProvider provider = getPersistenceProvider();
+ if (provider == null) {
+ String providerClassName = this.persistenceUnitInfo.getPersistenceProviderClassName();
+ if (providerClassName == null) {
+ throw new IllegalArgumentException(
+ "No PersistenceProvider specified in EntityManagerFactory configuration, " +
+ "and chosen PersistenceUnitInfo does not specify a provider class name either");
+ }
+ Class providerClass = ClassUtils.resolveClassName(providerClassName, getBeanClassLoader());
+ provider = (PersistenceProvider) BeanUtils.instantiateClass(providerClass);
+ }
+ if (provider == null) {
+ throw new IllegalStateException("Unable to determine persistence provider. " +
+ "Please check configuration of " + getClass().getName() + "; " +
+ "ideally specify the appropriate JpaVendorAdapter class for this provider.");
+ }
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Building JPA container EntityManagerFactory for persistence unit '" +
+ this.persistenceUnitInfo.getPersistenceUnitName() + "'");
+ }
+ this.nativeEntityManagerFactory =
+ provider.createContainerEntityManagerFactory(this.persistenceUnitInfo, getJpaPropertyMap());
+ postProcessEntityManagerFactory(this.nativeEntityManagerFactory, this.persistenceUnitInfo);
+
+ return this.nativeEntityManagerFactory;
+ }
+
+
+ /**
+ * Determine the PersistenceUnitInfo to use for the EntityManagerFactory
+ * created by this bean.
+ * The default implementation reads in all persistence unit infos from
+ * The default implementation is empty.
+ * @param emf the newly created EntityManagerFactory we are working with
+ * @param pui the PersistenceUnitInfo used to configure the EntityManagerFactory
+ * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory
+ */
+ protected void postProcessEntityManagerFactory(EntityManagerFactory emf, PersistenceUnitInfo pui) {
+ }
+
+
+ public PersistenceUnitInfo getPersistenceUnitInfo() {
+ return this.persistenceUnitInfo;
+ }
+
+ public String getPersistenceUnitName() {
+ if (this.persistenceUnitInfo != null) {
+ return this.persistenceUnitInfo.getPersistenceUnitName();
+ }
+ return super.getPersistenceUnitName();
+ }
+
+ public DataSource getDataSource() {
+ if (this.persistenceUnitInfo != null) {
+ return this.persistenceUnitInfo.getNonJtaDataSource();
+ }
+ return this.internalPersistenceUnitManager.getDefaultDataSource();
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java
new file mode 100644
index 00000000000..b4251557015
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2007 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.jpa;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.persistence.PersistenceException;
+import javax.persistence.spi.PersistenceProvider;
+
+/**
+ * {@link org.springframework.beans.factory.FactoryBean} that creates a JPA
+ * {@link javax.persistence.EntityManagerFactory} according to JPA's standard
+ * standalone bootstrap contract. This is the simplest way to set up a
+ * shared JPA EntityManagerFactory in a Spring application context; the
+ * EntityManagerFactory can then be passed to JPA-based DAOs via
+ * dependency injection. Note that switching to a JNDI lookup or to a
+ * {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean}
+ * definition is just a matter of configuration!
+ *
+ * Configuration settings are usually read from a This EntityManagerFactory bootstrap is appropriate for standalone applications
+ * which solely use JPA for data access. If you want to set up your persistence
+ * provider for an external DataSource and/or for global transactions which span
+ * multiple resources, you will need to either deploy it into a full Java EE 5
+ * application server and access the deployed EntityManagerFactory via JNDI,
+ * or use Spring's {@link LocalContainerEntityManagerFactoryBean} with appropriate
+ * configuration for local setup according to JPA's container contract.
+ *
+ * Note: This FactoryBean has limited configuration power in terms of
+ * what configuration it is able to pass to the JPA provider. If you need more
+ * flexible configuration, for example passing a Spring-managed JDBC DataSource
+ * to the JPA provider, consider using Spring's more powerful
+ * {@link LocalContainerEntityManagerFactoryBean} instead.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.0
+ * @see #setJpaProperties
+ * @see #setJpaVendorAdapter
+ * @see JpaTemplate#setEntityManagerFactory
+ * @see JpaTransactionManager#setEntityManagerFactory
+ * @see LocalContainerEntityManagerFactoryBean
+ * @see org.springframework.jndi.JndiObjectFactoryBean
+ * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
+ * @see javax.persistence.Persistence#createEntityManagerFactory
+ * @see javax.persistence.spi.PersistenceProvider#createEntityManagerFactory
+ */
+public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean {
+
+ /**
+ * Initialize the EntityManagerFactory for the given configuration.
+ * @throws javax.persistence.PersistenceException in case of JPA initialization errors
+ */
+ protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {
+ if (logger.isInfoEnabled()) {
+ logger.info("Building JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
+ }
+ PersistenceProvider provider = getPersistenceProvider();
+ if (provider != null) {
+ // Create EntityManagerFactory directly through PersistenceProvider.
+ EntityManagerFactory emf = provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap());
+ if (emf == null) {
+ throw new IllegalStateException(
+ "PersistenceProvider [" + provider + "] did not return an EntityManagerFactory for name '" +
+ getPersistenceUnitName() + "'");
+ }
+ return emf;
+ }
+ else {
+ // Let JPA perform its standard PersistenceProvider autodetection.
+ return Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap());
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java
new file mode 100644
index 00000000000..1e835850eed
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Query;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Factory for a shareable JPA {@link javax.persistence.EntityManager}
+ * for a given {@link javax.persistence.EntityManagerFactory}.
+ *
+ * The shareable EntityManager will behave just like an EntityManager fetched
+ * from an application server's JNDI environment, as defined by the JPA
+ * specification. It will delegate all calls to the current transactional
+ * EntityManager, if any; otherwise it will fall back to a newly created
+ * EntityManager per operation.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.0
+ * @see org.springframework.orm.jpa.LocalEntityManagerFactoryBean
+ * @see org.springframework.orm.jpa.JpaTransactionManager
+ */
+public abstract class SharedEntityManagerCreator {
+
+ /**
+ * Create a transactional EntityManager proxy for the given EntityManagerFactory.
+ * @param emf the EntityManagerFactory to delegate to.
+ * If this implements the {@link EntityManagerFactoryInfo} interface,
+ * appropriate handling of the native EntityManagerFactory and available
+ * {@link EntityManagerPlusOperations} will automatically apply.
+ * @return a shareable transaction EntityManager proxy
+ */
+ public static EntityManager createSharedEntityManager(EntityManagerFactory emf) {
+ return createSharedEntityManager(emf, null);
+ }
+
+ /**
+ * Create a transactional EntityManager proxy for the given EntityManagerFactory.
+ * @param emf the EntityManagerFactory to delegate to.
+ * If this implements the {@link EntityManagerFactoryInfo} interface,
+ * appropriate handling of the native EntityManagerFactory and available
+ * {@link EntityManagerPlusOperations} will automatically apply.
+ * @param properties the properties to be passed into the
+ * Supports standard JPA scanning for The default XML file location is Default is "classpath*:META-INF/persistence.xml".
+ */
+ public void setPersistenceXmlLocation(String persistenceXmlLocation) {
+ this.persistenceXmlLocations = new String[] {persistenceXmlLocation};
+ }
+
+ /**
+ * Specify multiple locations of Default is "classpath*:META-INF/persistence.xml".
+ * @param persistenceXmlLocations an array of Spring resource Strings
+ * identifying the location of the Default is "classpath:", that is, the root of the current class path
+ * (nearest root directory). To be overridden if unit-specific resolution
+ * does not work and the class path root is not appropriate either.
+ */
+ public void setDefaultPersistenceUnitRootLocation(String defaultPersistenceUnitRootLocation) {
+ this.defaultPersistenceUnitRootLocation = defaultPersistenceUnitRootLocation;
+ }
+
+ /**
+ * Specify the JDBC DataSources that the JPA persistence provider is supposed
+ * to use for accessing the database, resolving data source names in
+ * The specified Map needs to define data source names for specific DataSource
+ * objects, matching the data source names used in Default is JndiDataSourceLookup, which resolves DataSource names as
+ * JNDI names (as defined by standard JPA). Specify a BeanFactoryDataSourceLookup
+ * instance if you want DataSource names to be resolved against Spring bean names.
+ * Alternatively, consider passing in a map from names to DataSource instances
+ * via the "dataSources" property. If the In JPA speak, a DataSource passed in here will be uses as "nonJtaDataSource"
+ * on the PersistenceUnitInfo passed to the PersistenceProvider, provided that
+ * none has been registered before.
+ * @see javax.persistence.spi.PersistenceUnitInfo#getNonJtaDataSource()
+ */
+ public void setDefaultDataSource(DataSource defaultDataSource) {
+ this.defaultDataSource = defaultDataSource;
+ }
+
+ /**
+ * Return the JDBC DataSource that the JPA persistence provider is supposed
+ * to use for accessing the database if none has been specified in
+ * Such post-processors can, for example, register further entity
+ * classes and jar files, in addition to the metadata read in from
+ * It is not required to specify a LoadTimeWeaver: Most providers will be
+ * able to provide a subset of their functionality without class instrumentation
+ * as well, or operate with their VM agent specified on JVM startup.
+ * In terms of Spring-provided weaving options, the most important ones are
+ * InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general)
+ * VM agent specified on JVM startup, and ReflectiveLoadTimeWeaver, which interacts
+ * with an underlying ClassLoader based on specific extended methods being available
+ * on it (for example, interacting with Spring's TomcatInstrumentableClassLoader).
+ * NOTE: As of Spring 2.5, the context's default LoadTimeWeaver (defined
+ * as bean with name "loadTimeWeaver") will be picked up automatically, if available,
+ * removing the need for LoadTimeWeaver configuration on each affected target bean.
+ * Consider using the PersistenceUnitInfos cannot be obtained before this preparation
+ * method has been invoked.
+ * @see #obtainDefaultPersistenceUnitInfo()
+ * @see #obtainPersistenceUnitInfo(String)
+ */
+ public void preparePersistenceUnitInfos() {
+ this.persistenceUnitInfoNames.clear();
+ this.persistenceUnitInfos.clear();
+ SpringPersistenceUnitInfo[] puis = readPersistenceUnitInfos();
+ for (int i = 0; i < puis.length; i++) {
+ SpringPersistenceUnitInfo pui = puis[i];
+ if (pui.getPersistenceUnitRootUrl() == null) {
+ pui.setPersistenceUnitRootUrl(determineDefaultPersistenceUnitRootUrl());
+ }
+ if (pui.getNonJtaDataSource() == null) {
+ pui.setNonJtaDataSource(this.defaultDataSource);
+ }
+ if (this.loadTimeWeaver != null) {
+ pui.init(this.loadTimeWeaver);
+ }
+ else {
+ pui.init(this.resourcePatternResolver.getClassLoader());
+ }
+ postProcessPersistenceUnitInfo(pui);
+ String name = pui.getPersistenceUnitName();
+ this.persistenceUnitInfoNames.add(name);
+ this.persistenceUnitInfos.put(name, pui);
+ }
+ }
+
+ /**
+ * Read all persistence unit infos from This can be used in {@link #postProcessPersistenceUnitInfo} implementations,
+ * detecting existing persistence units of the same name and potentially merging them.
+ * @param persistenceUnitName the name of the desired persistence unit
+ * @return the PersistenceUnitInfo in mutable form, or Default implementation delegates to all registered PersistenceUnitPostProcessors.
+ * It is usually preferable to register further entity classes, jar files etc there
+ * rather than in a subclass of this manager, to be able to reuse the post-processors.
+ * @param pui the chosen PersistenceUnitInfo, as read from This implementation is largely a JavaBean, offering mutators
+ * for all standard PersistenceUnitInfo properties.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Costin Leau
+ * @since 2.0
+ */
+public class MutablePersistenceUnitInfo implements PersistenceUnitInfo {
+
+ private String persistenceUnitName;
+
+ private String persistenceProviderClassName;
+
+ private PersistenceUnitTransactionType transactionType;
+
+ private DataSource nonJtaDataSource;
+
+ private DataSource jtaDataSource;
+
+ private List Obtaining a PersistenceUnitInfo instance is an exclusive process.
+ * A PersistenceUnitInfo instance is not available for further calls
+ * anymore once it has been obtained.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see DefaultPersistenceUnitManager
+ * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setPersistenceUnitManager
+ */
+public interface PersistenceUnitManager {
+
+ /**
+ * Obtain the default PersistenceUnitInfo from this manager.
+ * @return the PersistenceUnitInfo (never This class is restricted to package visibility, in contrast to its superclass.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Costin Leau
+ * @since 2.0
+ * @see PersistenceUnitManager
+ */
+class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo {
+
+ private LoadTimeWeaver loadTimeWeaver;
+
+ private ClassLoader classLoader;
+
+
+ /**
+ * Initialize this PersistenceUnitInfo with the LoadTimeWeaver SPI interface
+ * used by Spring to add instrumentation to the current class loader.
+ */
+ public void init(LoadTimeWeaver loadTimeWeaver) {
+ Assert.notNull(loadTimeWeaver, "LoadTimeWeaver must not be null");
+ this.loadTimeWeaver = loadTimeWeaver;
+ this.classLoader = loadTimeWeaver.getInstrumentableClassLoader();
+ }
+
+ /**
+ * Initialize this PersistenceUnitInfo with the current class loader
+ * (instead of with a LoadTimeWeaver).
+ */
+ public void init(ClassLoader classLoader) {
+ Assert.notNull(classLoader, "ClassLoader must not be null");
+ this.classLoader = classLoader;
+ }
+
+
+ /**
+ * This implementation returns the LoadTimeWeaver's instrumentable ClassLoader,
+ * if specified.
+ */
+ public ClassLoader getClassLoader() {
+ return this.classLoader;
+ }
+
+ /**
+ * This implementation delegates to the LoadTimeWeaver, if specified.
+ */
+ public void addTransformer(ClassTransformer classTransformer) {
+ if (this.loadTimeWeaver == null) {
+ throw new IllegalStateException("Cannot apply class transformer without LoadTimeWeaver specified");
+ }
+ this.loadTimeWeaver.addTransformer(new ClassFileTransformerAdapter(classTransformer));
+ }
+
+ /**
+ * This implementation delegates to the LoadTimeWeaver, if specified.
+ */
+ public ClassLoader getNewTempClassLoader() {
+ ClassLoader tcl = (this.loadTimeWeaver != null ? this.loadTimeWeaver.getThrowawayClassLoader() :
+ new SimpleThrowawayClassLoader(this.classLoader));
+ String packageToExclude = getPersistenceProviderPackageName();
+ if (packageToExclude != null && tcl instanceof DecoratingClassLoader) {
+ ((DecoratingClassLoader) tcl).excludePackage(packageToExclude);
+ }
+ return tcl;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/package.html
new file mode 100644
index 00000000000..5eab4decae0
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/package.html
@@ -0,0 +1,7 @@
+
+ Requires an EntityManagerFactory or EntityManager to be set,
+ * providing a JpaTemplate based on it to subclasses. Can alternatively
+ * be initialized directly via a JpaTemplate, to reuse the latter's
+ * settings such as the EntityManagerFactory, JpaDialect, flush mode, etc.
+ *
+ * This class will create its own JpaTemplate if an EntityManagerFactory
+ * or EntityManager reference is passed in. A custom JpaTemplate instance
+ * can be used through overriding Can be overridden in subclasses to provide a JpaTemplate instance
+ * with different configuration, or a custom JpaTemplate subclass.
+ * @param entityManagerFactory the JPA EntityManagerFactory to create a JpaTemplate for
+ * @return the new JpaTemplate instance
+ * @see #setEntityManagerFactory
+ */
+ protected JpaTemplate createJpaTemplate(EntityManagerFactory entityManagerFactory) {
+ return new JpaTemplate(entityManagerFactory);
+ }
+
+ /**
+ * Set the JPA EntityManager to be used by this DAO.
+ * Will automatically create a JpaTemplate for the given EntityManager.
+ * @see #createJpaTemplate
+ * @see #setJpaTemplate
+ */
+ public final void setEntityManager(EntityManager entityManager) {
+ this.jpaTemplate = createJpaTemplate(entityManager);
+ }
+
+ /**
+ * Create a JpaTemplate for the given EntityManager.
+ * Only invoked if populating the DAO with a EntityManager reference!
+ * Can be overridden in subclasses to provide a JpaTemplate instance
+ * with different configuration, or a custom JpaTemplate subclass.
+ * @param entityManager the JPA EntityManager to create a JpaTemplate for
+ * @return the new JpaTemplate instance
+ * @see #setEntityManagerFactory
+ */
+ protected JpaTemplate createJpaTemplate(EntityManager entityManager) {
+ return new JpaTemplate(entityManager);
+ }
+
+ /**
+ * Set the JpaTemplate for this DAO explicitly,
+ * as an alternative to specifying a EntityManagerFactory.
+ * @see #setEntityManagerFactory
+ */
+ public final void setJpaTemplate(JpaTemplate jpaTemplate) {
+ this.jpaTemplate = jpaTemplate;
+ }
+
+ /**
+ * Return the JpaTemplate for this DAO, pre-initialized
+ * with the EntityManagerFactory or set explicitly.
+ */
+ public final JpaTemplate getJpaTemplate() {
+ return jpaTemplate;
+ }
+
+ protected final void checkDaoConfig() {
+ if (this.jpaTemplate == null) {
+ throw new IllegalArgumentException("entityManagerFactory or jpaTemplate is required");
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java
new file mode 100644
index 00000000000..d09a997153d
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa.support;
+
+import java.io.IOException;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.orm.jpa.EntityManagerHolder;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+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 JPA EntityManager to the thread for the
+ * entire processing of the request. Intended for the "Open EntityManager in
+ * View" pattern, i.e. to allow for lazy loading in web views despite the
+ * original transactions already being completed.
+ *
+ * This filter makes JPA EntityManagers available via the current thread,
+ * which will be autodetected by transaction managers. It is suitable for service
+ * layer transactions via {@link org.springframework.orm.jpa.JpaTransactionManager}
+ * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
+ * as for non-transactional read-only execution.
+ *
+ * Looks up the EntityManagerFactory in Spring's root web application context.
+ * Supports a "entityManagerFactoryBeanName" filter init-param in Default implementation delegates to the Can be overridden in subclasses.
+ * @param emf the EntityManagerFactory to use
+ * @see javax.persistence.EntityManagerFactory#createEntityManager()
+ */
+ protected EntityManager createEntityManager(EntityManagerFactory emf) {
+ return emf.createEntityManager();
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java
new file mode 100644
index 00000000000..c17a40da348
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa.support;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.orm.jpa.EntityManagerFactoryAccessor;
+import org.springframework.orm.jpa.EntityManagerHolder;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+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 JPA EntityManager to the
+ * thread for the entire processing of the request. Intended for the "Open
+ * EntityManager in View" pattern, i.e. to allow for lazy loading in
+ * web views despite the original transactions already being completed.
+ *
+ * This interceptor makes JPA EntityManagers available via the current thread,
+ * which will be autodetected by transaction managers. It is suitable for service
+ * layer transactions via {@link org.springframework.orm.jpa.JpaTransactionManager}
+ * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
+ * as for non-transactional read-only execution.
+ *
+ * In contrast to {@link OpenEntityManagerInViewFilter}, this interceptor
+ * is set up in a Spring application context and can thus take advantage of
+ * bean wiring. It inherits common JPA configuration properties from
+ * {@link org.springframework.orm.jpa.JpaAccessor}, to be configured in a
+ * bean definition.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see OpenEntityManagerInViewFilter
+ * @see org.springframework.orm.jpa.JpaInterceptor
+ * @see org.springframework.orm.jpa.JpaTransactionManager
+ * @see org.springframework.orm.jpa.JpaTemplate#execute
+ * @see org.springframework.orm.jpa.SharedEntityManagerCreator
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements WebRequestInterceptor {
+
+ /**
+ * Suffix that gets appended to the EntityManagerFactory toString
+ * representation for the "participate in existing entity manager
+ * handling" request attribute.
+ * @see #getParticipateAttributeName
+ */
+ public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
+
+
+ public void preHandle(WebRequest request) throws DataAccessException {
+ if (TransactionSynchronizationManager.hasResource(getEntityManagerFactory())) {
+ // do not modify the EntityManager: just mark the request accordingly
+ String participateAttributeName = getParticipateAttributeName();
+ Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
+ int newCount = (count != null) ? count.intValue() + 1 : 1;
+ request.setAttribute(getParticipateAttributeName(), new Integer(newCount), WebRequest.SCOPE_REQUEST);
+ }
+ else {
+ logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
+ try {
+ EntityManager em = createEntityManager();
+ TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), new EntityManagerHolder(em));
+ }
+ catch (PersistenceException ex) {
+ throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
+ }
+ }
+ }
+
+ public void postHandle(WebRequest request, ModelMap model) {
+ }
+
+ 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 EntityManager: just clear the marker.
+ if (count.intValue() > 1) {
+ request.setAttribute(participateAttributeName, new Integer(count.intValue() - 1), WebRequest.SCOPE_REQUEST);
+ }
+ else {
+ request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
+ }
+ }
+ else {
+ EntityManagerHolder emHolder = (EntityManagerHolder)
+ TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
+ logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
+ EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
+ }
+ }
+
+ /**
+ * Return the name of the request attribute that identifies that a request is
+ * already filtered. Default implementation takes the toString representation
+ * of the EntityManagerFactory instance and appends ".FILTERED".
+ * @see #PARTICIPATE_SUFFIX
+ */
+ protected String getParticipateAttributeName() {
+ return getEntityManagerFactory().toString() + PARTICIPATE_SUFFIX;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java
new file mode 100644
index 00000000000..3ba4a0d35e3
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa.support;
+
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.naming.NamingException;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceContext;
+import javax.persistence.PersistenceContextType;
+import javax.persistence.PersistenceProperty;
+import javax.persistence.PersistenceUnit;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.PropertyValues;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.InjectionMetadata;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
+import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
+import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.Ordered;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.jndi.JndiLocatorSupport;
+import org.springframework.orm.jpa.EntityManagerFactoryInfo;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+import org.springframework.orm.jpa.EntityManagerProxy;
+import org.springframework.orm.jpa.ExtendedEntityManagerCreator;
+import org.springframework.orm.jpa.SharedEntityManagerCreator;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * BeanPostProcessor that processes {@link javax.persistence.PersistenceUnit}
+ * and {@link javax.persistence.PersistenceContext} annotations, for injection of
+ * the corresponding JPA resources {@link javax.persistence.EntityManagerFactory}
+ * and {@link javax.persistence.EntityManager}. Any such annotated fields or methods
+ * in any Spring-managed object will automatically be injected.
+ *
+ * This post-processor will inject sub-interfaces of Note: In the present implementation, PersistenceAnnotationBeanPostProcessor
+ * only supports This post-processor can either obtain EntityManagerFactory beans defined
+ * in the Spring application context (the default), or obtain EntityManagerFactory
+ * references from JNDI ("persistence unit references"). In the bean case,
+ * the persistence unit name will be matched against the actual deployed unit,
+ * with the bean name used as fallback unit name if no deployed name found.
+ * Typically, Spring's {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean}
+ * will be used for setting up such EntityManagerFactory beans. Alternatively,
+ * such beans may also be obtained from JNDI, e.g. using the If you prefer the Java EE server's own EntityManager handling, specify entries
+ * in this post-processor's {@link #setPersistenceContexts "persistenceContexts" map}
+ * (or {@link #setExtendedPersistenceContexts "extendedPersistenceContexts" map},
+ * typically with matching NOTE: In general, do not inject EXTENDED EntityManagers into STATELESS beans,
+ * i.e. do not use Note: A default PersistenceAnnotationBeanPostProcessor will be registered
+ * by the "context:annotation-config" and "context:component-scan" XML tags.
+ * Remove or turn off the default annotation configuration there if you intend
+ * to specify a custom PersistenceAnnotationBeanPostProcessor bean definition.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see javax.persistence.PersistenceUnit
+ * @see javax.persistence.PersistenceContext
+ */
+public class PersistenceAnnotationBeanPostProcessor extends JndiLocatorSupport
+ implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor,
+ MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware, Serializable {
+
+ private transient Mapjavax.persistence.EntityManager
+ * interface else.
+ * @see JpaVendorAdapter#getEntityManagerInterface()
+ * @see EntityManagerFactoryInfo#getEntityManagerInterface()
+ */
+ public void setEntityManagerInterface(Class extends EntityManager> emInterface) {
+ Assert.isAssignable(EntityManager.class, emInterface);
+ this.entityManagerInterface = emInterface;
+ }
+
+ public Class extends EntityManager> getEntityManagerInterface() {
+ return this.entityManagerInterface;
+ }
+
+ /**
+ * Specify the vendor-specific JpaDialect implementation to associate with
+ * this EntityManagerFactory. This will be exposed through the
+ * EntityManagerFactoryInfo interface, to be picked up as default dialect by
+ * accessors that intend to use JpaDialect functionality.
+ * @see EntityManagerFactoryInfo#getJpaDialect()
+ */
+ public void setJpaDialect(JpaDialect jpaDialect) {
+ this.jpaDialect = jpaDialect;
+ }
+
+ public JpaDialect getJpaDialect() {
+ return this.jpaDialect;
+ }
+
+ /**
+ * Specify the JpaVendorAdapter implementation for the desired JPA provider,
+ * if any. This will initialize appropriate defaults for the given provider,
+ * such as persistence provider class and JpaDialect, unless locally
+ * overridden in this FactoryBean.
+ */
+ public void setJpaVendorAdapter(JpaVendorAdapter jpaVendorAdapter) {
+ this.jpaVendorAdapter = jpaVendorAdapter;
+ }
+
+ /**
+ * Return the JpaVendorAdapter implementation for this
+ * EntityManagerFactory, or null if not known.
+ */
+ public JpaVendorAdapter getJpaVendorAdapter() {
+ return this.jpaVendorAdapter;
+ }
+
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.beanClassLoader = classLoader;
+ }
+
+ public ClassLoader getBeanClassLoader() {
+ return this.beanClassLoader;
+ }
+
+
+ public final void afterPropertiesSet() throws PersistenceException {
+ if (this.jpaVendorAdapter != null) {
+ if (this.persistenceProvider == null) {
+ this.persistenceProvider = this.jpaVendorAdapter.getPersistenceProvider();
+ }
+ Map vendorPropertyMap = this.jpaVendorAdapter.getJpaPropertyMap();
+ if (vendorPropertyMap != null) {
+ for (Iterator it = vendorPropertyMap.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ if (!this.jpaPropertyMap.containsKey(entry.getKey())) {
+ this.jpaPropertyMap.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ if (this.entityManagerFactoryInterface == null) {
+ this.entityManagerFactoryInterface = this.jpaVendorAdapter.getEntityManagerFactoryInterface();
+ if (!ClassUtils.isVisible(this.entityManagerFactoryInterface, this.beanClassLoader)) {
+ this.entityManagerFactoryInterface = EntityManagerFactory.class;
+ }
+ }
+ if (this.entityManagerInterface == null) {
+ this.entityManagerInterface = this.jpaVendorAdapter.getEntityManagerInterface();
+ if (!ClassUtils.isVisible(this.entityManagerInterface, this.beanClassLoader)) {
+ this.entityManagerInterface = EntityManager.class;
+ }
+ }
+ if (this.jpaDialect == null) {
+ this.jpaDialect = this.jpaVendorAdapter.getJpaDialect();
+ }
+ }
+
+ this.nativeEntityManagerFactory = createNativeEntityManagerFactory();
+ if (this.nativeEntityManagerFactory == null) {
+ throw new IllegalStateException(
+ "JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
+ }
+ if (this.jpaVendorAdapter != null) {
+ this.jpaVendorAdapter.postProcessEntityManagerFactory(this.nativeEntityManagerFactory);
+ }
+
+ // Wrap the EntityManagerFactory in a factory implementing all its interfaces.
+ // This allows interception of createEntityManager methods to return an
+ // application-managed EntityManager proxy that automatically joins
+ // existing transactions.
+ this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
+ }
+
+ /**
+ * Create a proxy of the given EntityManagerFactory. We do this to be able
+ * to return transaction-aware proxies for application-managed
+ * EntityManagers, and to introduce the NamedEntityManagerFactory interface
+ * @param emf EntityManagerFactory as returned by the persistence provider
+ * @return proxy entity manager
+ */
+ protected EntityManagerFactory createEntityManagerFactoryProxy(EntityManagerFactory emf) {
+ SetgetObject() method.
+ * @return EntityManagerFactory instance returned by this FactoryBean
+ * @throws PersistenceException if the EntityManager cannot be created
+ */
+ protected abstract EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException;
+
+
+ /**
+ * Implementation of the PersistenceExceptionTranslator interface, as
+ * autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
+ * Transaction.begin
+ * method. Throws an InvalidIsolationLevelException if a non-default isolation
+ * level is set.
+ * null) of this implementation
+ * and are free to return their own transaction data Object.
+ * @see javax.persistence.EntityTransaction#begin
+ * @see org.springframework.transaction.InvalidIsolationLevelException
+ * @see #cleanupTransaction
+ */
+ public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
+ throws PersistenceException, SQLException, TransactionException {
+
+ if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
+ throw new InvalidIsolationLevelException(
+ "Standard JPA does not support custom isolation levels - " +
+ "use a special JpaDialect for your JPA implementation");
+ }
+ entityManager.getTransaction().begin();
+ return null;
+ }
+
+ public Object prepareTransaction(EntityManager entityManager, boolean readOnly, String name)
+ throws PersistenceException {
+
+ return null;
+ }
+
+ /**
+ * This implementation does nothing, since the default beginTransaction
+ * implementation does not require any cleanup.
+ * @see #beginTransaction
+ */
+ public void cleanupTransaction(Object transactionData) {
+ }
+
+ /**
+ * This implementation always returns null,
+ * indicating that no JDBC Connection can be provided.
+ */
+ public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly)
+ throws PersistenceException, SQLException {
+
+ return null;
+ }
+
+ /**
+ * This implementation does nothing, assuming that the Connection
+ * will implicitly be closed with the EntityManager.
+ * Connection.close() (or some other method with similar effect) here.
+ * @see java.sql.Connection#close()
+ */
+ public void releaseJdbcConnection(ConnectionHandle conHandle, EntityManager em)
+ throws PersistenceException, SQLException {
+ }
+
+
+ //-----------------------------------------------------------------------------------
+ // Hook for exception translation (used by JpaTransactionManager and JpaTemplate)
+ //-----------------------------------------------------------------------------------
+
+ /**
+ * This implementation delegates to EntityManagerFactoryUtils.
+ * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
+ */
+ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+ return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
+ }
+
+
+ public boolean supportsEntityManagerFactoryPlusOperations() {
+ return false;
+ }
+
+ public boolean supportsEntityManagerPlusOperations() {
+ return false;
+ }
+
+ public EntityManagerFactoryPlusOperations getEntityManagerFactoryPlusOperations(EntityManagerFactory rawEntityManager) {
+ throw new UnsupportedOperationException(getClass().getName() + " does not support EntityManagerFactoryPlusOperations");
+ }
+
+ public EntityManagerPlusOperations getEntityManagerPlusOperations(EntityManager rawEntityManager) {
+ throw new UnsupportedOperationException(getClass().getName() + " does not support EntityManagerPlusOperations");
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java
new file mode 100644
index 00000000000..a21cfaf514e
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Base class for any class that needs to access an EntityManagerFactory,
+ * usually in order to obtain an EntityManager. Defines common properties.
+ *
+ * EntityManagerFactory.createEntityManager(Map) (if any).
+ * EntityManagerFactory.createEntityManager(Map) (if any).
+ * null if none
+ * @throws IllegalStateException if this accessor is not configured with an EntityManagerFactory
+ * @see EntityManagerFactoryUtils#getTransactionalEntityManager(javax.persistence.EntityManagerFactory)
+ * @see EntityManagerFactoryUtils#getTransactionalEntityManager(javax.persistence.EntityManagerFactory, java.util.Map)
+ */
+ protected EntityManager getTransactionalEntityManager() throws IllegalStateException{
+ EntityManagerFactory emf = getEntityManagerFactory();
+ Assert.state(emf != null, "No EntityManagerFactory specified");
+ return EntityManagerFactoryUtils.getTransactionalEntityManager(emf, getJpaPropertyMap());
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java
new file mode 100644
index 00000000000..83cf10d169f
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.spi.PersistenceProvider;
+import javax.persistence.spi.PersistenceUnitInfo;
+import javax.sql.DataSource;
+
+/**
+ * Metadata interface for a Spring-managed JPA {@link EntityManagerFactory}.
+ *
+ * null)
+ */
+ EntityManagerFactory getNativeEntityManagerFactory();
+
+ /**
+ * Return the underlying PersistenceProvider that the underlying
+ * EntityManagerFactory was created with.
+ * @return the PersistenceProvider used to create this EntityManagerFactory,
+ * or null if the standard JPA provider autodetection process
+ * was used to configure the EntityManagerFactory
+ */
+ PersistenceProvider getPersistenceProvider();
+
+ /**
+ * Return the PersistenceUnitInfo used to create this
+ * EntityManagerFactory, if the in-container API was used.
+ * @return the PersistenceUnitInfo used to create this EntityManagerFactory,
+ * or null if the in-container contract was not used to
+ * configure the EntityManagerFactory
+ */
+ PersistenceUnitInfo getPersistenceUnitInfo();
+
+ /**
+ * Return the name of the persistence unit used to create this
+ * EntityManagerFactory, or null if
+ * it is an unnamed default. If getPersistenceUnitInfo()
+ * returns non-null, the return type of getPersistenceUnitName()
+ * must be equal to the value returned by
+ * PersistenceUnitInfo.getPersistenceUnitName().
+ * @see #getPersistenceUnitInfo()
+ * @see javax.persistence.spi.PersistenceUnitInfo#getPersistenceUnitName()
+ */
+ String getPersistenceUnitName();
+
+ /**
+ * Return the JDBC DataSource that this EntityManagerFactory
+ * obtains its JDBC Connections from.
+ * @return the JDBC DataSource, or null if not known
+ */
+ DataSource getDataSource();
+
+ /**
+ * Return the (potentially vendor-specific) EntityManager interface
+ * that this factory's EntityManagers will implement.
+ * null return value suggests that autodetection is supposed
+ * to happen: either based on a target EntityManager instance
+ * or simply defaulting to javax.persistence.EntityManager.
+ */
+ Class extends EntityManager> getEntityManagerInterface();
+
+ /**
+ * Return the vendor-specific JpaDialect implementation for this
+ * EntityManagerFactory, or null if not known.
+ */
+ JpaDialect getJpaDialect();
+
+ /**
+ * Return the ClassLoader that the application's beans are loaded with.
+ * null if no thread-bound EntityManager found!
+ * @param emf EntityManagerFactory to create the EntityManager with
+ * @return the EntityManager, or null if none found
+ * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained
+ * @see JpaTransactionManager
+ */
+ public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf)
+ throws DataAccessResourceFailureException {
+
+ return getTransactionalEntityManager(emf, null);
+ }
+
+ /**
+ * Obtain a JPA EntityManager from the given factory. Is aware of a
+ * corresponding EntityManager bound to the current thread,
+ * for example when using JpaTransactionManager.
+ * null if no thread-bound EntityManager found!
+ * @param emf EntityManagerFactory to create the EntityManager with
+ * @param properties the properties to be passed into the createEntityManager
+ * call (may be null)
+ * @return the EntityManager, or null if none found
+ * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained
+ * @see JpaTransactionManager
+ */
+ public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf, Map properties)
+ throws DataAccessResourceFailureException {
+ try {
+ return doGetTransactionalEntityManager(emf, properties);
+ }
+ catch (PersistenceException ex) {
+ throw new DataAccessResourceFailureException("Could not obtain JPA EntityManager", ex);
+ }
+ }
+
+ /**
+ * Obtain a JPA EntityManager from the given factory. Is aware of a
+ * corresponding EntityManager bound to the current thread,
+ * for example when using JpaTransactionManager.
+ * getEntityManager, but throwing the original PersistenceException.
+ * @param emf EntityManagerFactory to create the EntityManager with
+ * @param properties the properties to be passed into the createEntityManager
+ * call (may be null)
+ * @return the EntityManager, or null if none found
+ * @throws javax.persistence.PersistenceException if the EntityManager couldn't be created
+ * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory)
+ * @see JpaTransactionManager
+ */
+ public static EntityManager doGetTransactionalEntityManager(
+ EntityManagerFactory emf, Map properties) throws PersistenceException {
+
+ Assert.notNull(emf, "No EntityManagerFactory specified");
+
+ EntityManagerHolder emHolder =
+ (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
+ if (emHolder != null) {
+ if (!emHolder.isSynchronizedWithTransaction() &&
+ TransactionSynchronizationManager.isSynchronizationActive()) {
+ // Try to explicitly synchronize the EntityManager itself
+ // with an ongoing JTA transaction, if any.
+ try {
+ emHolder.getEntityManager().joinTransaction();
+ }
+ catch (TransactionRequiredException ex) {
+ logger.debug("Could not join JTA transaction because none was active", ex);
+ }
+ Object transactionData = prepareTransaction(emHolder.getEntityManager(), emf);
+ TransactionSynchronizationManager.registerSynchronization(
+ new EntityManagerSynchronization(emHolder, emf, transactionData, false));
+ emHolder.setSynchronizedWithTransaction(true);
+ }
+ return emHolder.getEntityManager();
+ }
+
+ if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+ // Indicate that we can't obtain a transactional EntityManager.
+ return null;
+ }
+
+ // Create a new EntityManager for use within the current transaction.
+ logger.debug("Opening JPA EntityManager");
+ EntityManager em =
+ (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
+
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ logger.debug("Registering transaction synchronization for JPA EntityManager");
+ // Use same EntityManager for further JPA actions within the transaction.
+ // Thread object will get removed by synchronization at transaction completion.
+ emHolder = new EntityManagerHolder(em);
+ Object transactionData = prepareTransaction(em, emf);
+ TransactionSynchronizationManager.registerSynchronization(
+ new EntityManagerSynchronization(emHolder, emf, transactionData, true));
+ emHolder.setSynchronizedWithTransaction(true);
+ TransactionSynchronizationManager.bindResource(emf, emHolder);
+ }
+
+ return em;
+ }
+
+ /**
+ * Prepare a transaction on the given EntityManager, if possible.
+ * @param em the EntityManager to prepare
+ * @param emf the EntityManagerFactory that the EntityManager has been created with
+ * @return an arbitrary object that holds transaction data, if any
+ * (to be passed into cleanupTransaction)
+ * @see JpaDialect#prepareTransaction
+ */
+ private static Object prepareTransaction(EntityManager em, EntityManagerFactory emf) {
+ if (emf instanceof EntityManagerFactoryInfo) {
+ EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
+ JpaDialect jpaDialect = emfInfo.getJpaDialect();
+ if (jpaDialect != null) {
+ return jpaDialect.prepareTransaction(em,
+ TransactionSynchronizationManager.isCurrentTransactionReadOnly(),
+ TransactionSynchronizationManager.getCurrentTransactionName());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Prepare a transaction on the given EntityManager, if possible.
+ * @param transactionData arbitrary object that holds transaction data, if any
+ * (as returned by prepareTransaction)
+ * @param emf the EntityManagerFactory that the EntityManager has been created with
+ * @see JpaDialect#cleanupTransaction
+ */
+ private static void cleanupTransaction(Object transactionData, EntityManagerFactory emf) {
+ if (emf instanceof EntityManagerFactoryInfo) {
+ EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
+ JpaDialect jpaDialect = emfInfo.getJpaDialect();
+ if (jpaDialect != null) {
+ jpaDialect.cleanupTransaction(transactionData);
+ }
+ }
+ }
+
+ /**
+ * Convert the given runtime exception to an appropriate exception from the
+ * org.springframework.dao hierarchy.
+ * Return null if no translation is appropriate: any other exception may
+ * have resulted from user code, and should not be translated.
+ * null if the exception should not be translated
+ */
+ public static DataAccessException convertJpaAccessExceptionIfPossible(RuntimeException ex) {
+ // Following the JPA specification, a persistence provider can also
+ // throw these two exceptions, besides PersistenceException.
+ if (ex instanceof IllegalStateException) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+ if (ex instanceof IllegalArgumentException) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+
+ // Check for well-known PersistenceException subclasses.
+ if (ex instanceof EntityNotFoundException) {
+ return new JpaObjectRetrievalFailureException((EntityNotFoundException) ex);
+ }
+ if (ex instanceof NoResultException) {
+ return new EmptyResultDataAccessException(ex.getMessage(), 1);
+ }
+ if (ex instanceof NonUniqueResultException) {
+ return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1);
+ }
+ if (ex instanceof OptimisticLockException) {
+ return new JpaOptimisticLockingFailureException((OptimisticLockException) ex);
+ }
+ if (ex instanceof EntityExistsException) {
+ return new DataIntegrityViolationException(ex.getMessage(), ex);
+ }
+ if (ex instanceof TransactionRequiredException) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+
+ // If we have another kind of PersistenceException, throw it.
+ if (ex instanceof PersistenceException) {
+ return new JpaSystemException((PersistenceException) ex);
+ }
+
+ // If we get here, we have an exception that resulted from user code,
+ // rather than the persistence provider, so we return null to indicate
+ // that translation should not occur.
+ return null;
+ }
+
+ /**
+ * Close the given JPA EntityManager,
+ * catching and logging any cleanup exceptions thrown.
+ * @param em the JPA EntityManager to close (may be null)
+ * @see javax.persistence.EntityManager#close()
+ */
+ public static void closeEntityManager(EntityManager em) {
+ if (em != null) {
+ logger.debug("Closing JPA EntityManager");
+ try {
+ em.close();
+ }
+ catch (PersistenceException ex) {
+ logger.debug("Could not close JPA EntityManager", ex);
+ }
+ catch (Throwable ex) {
+ logger.debug("Unexpected exception on closing JPA EntityManager", ex);
+ }
+ }
+ }
+
+
+ /**
+ * Callback for resource cleanup at the end of a non-JPA transaction
+ * (e.g. when participating in a JtaTransactionManager transaction).
+ * @see org.springframework.transaction.jta.JtaTransactionManager
+ */
+ private static class EntityManagerSynchronization extends ResourceHolderSynchronization implements Ordered {
+
+ private final Object transactionData;
+
+ private final boolean newEntityManager;
+
+ public EntityManagerSynchronization(
+ EntityManagerHolder emHolder, EntityManagerFactory emf, Object transactionData, boolean newEntityManager) {
+ super(emHolder, emf);
+ this.transactionData = transactionData;
+ this.newEntityManager = newEntityManager;
+ }
+
+ public int getOrder() {
+ return ENTITY_MANAGER_SYNCHRONIZATION_ORDER;
+ }
+
+ protected boolean shouldUnbindAtCompletion() {
+ return this.newEntityManager;
+ }
+
+ protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
+ closeEntityManager(((EntityManagerHolder) resourceHolder).getEntityManager());
+ }
+
+ protected void cleanupResource(ResourceHolder resourceHolder, Object resourceKey, boolean committed) {
+ if (!committed) {
+ // Clear all pending inserts/updates/deletes in the EntityManager.
+ // Necessary for pre-bound EntityManagers, to avoid inconsistent state.
+ ((EntityManagerHolder) resourceHolder).getEntityManager().clear();
+ }
+ cleanupTransaction(this.transactionData, (EntityManagerFactory) resourceKey);
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java
new file mode 100644
index 00000000000..a17f686902e
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2007 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.jpa;
+
+import javax.persistence.EntityManager;
+
+import org.springframework.transaction.SavepointManager;
+import org.springframework.transaction.support.ResourceHolderSupport;
+import org.springframework.util.Assert;
+
+/**
+ * Holder wrapping a JPA EntityManager.
+ * JpaTransactionManager binds instances of this class to the thread,
+ * for a given EntityManagerFactory.
+ *
+ * null)
+ * @throws IllegalStateException if no underlying EntityManager is available
+ */
+ EntityManager getTargetEntityManager() throws IllegalStateException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java
new file mode 100644
index 00000000000..aeb3eb484af
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.TransactionRequiredException;
+import javax.persistence.spi.PersistenceUnitInfo;
+import javax.persistence.spi.PersistenceUnitTransactionType;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.Ordered;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.transaction.support.ResourceHolderSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Factory for dynamic EntityManager proxies that follow the JPA spec's
+ * semantics for "extended" EntityManagers.
+ *
+ * joinTransaction() method ("application-managed extended
+ * EntityManager") as well as automatic joining on each operation
+ * ("container-managed extended EntityManager").
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class ExtendedEntityManagerCreator {
+
+ /**
+ * Create an EntityManager that can join transactions with the
+ * joinTransaction() method, but is not automatically
+ * managed by the container.
+ * @param rawEntityManager raw EntityManager
+ * @param plusOperations an implementation of the EntityManagerPlusOperations
+ * interface, if those operations should be exposed (may be null)
+ * @return an application-managed EntityManager that can join transactions
+ * but does not participate in them automatically
+ */
+ public static EntityManager createApplicationManagedEntityManager(
+ EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations) {
+
+ return createProxy(rawEntityManager, null, null, plusOperations, null, null, false);
+ }
+
+ /**
+ * Create an EntityManager that can join transactions with the
+ * joinTransaction() method, but is not automatically
+ * managed by the container.
+ * @param rawEntityManager raw EntityManager
+ * @param plusOperations an implementation of the EntityManagerPlusOperations
+ * interface, if those operations should be exposed (may be null)
+ * @param exceptionTranslator the exception translator to use for translating
+ * JPA commit/rollback exceptions during transaction synchronization
+ * (may be null)
+ * @return an application-managed EntityManager that can join transactions
+ * but does not participate in them automatically
+ */
+ public static EntityManager createApplicationManagedEntityManager(
+ EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations,
+ PersistenceExceptionTranslator exceptionTranslator) {
+
+ return createProxy(rawEntityManager, null, null, plusOperations, exceptionTranslator, null, false);
+ }
+
+ /**
+ * Create an EntityManager that can join transactions with the
+ * joinTransaction() method, but is not automatically
+ * managed by the container.
+ * @param rawEntityManager raw EntityManager
+ * @param emfInfo the EntityManagerFactoryInfo to obtain the
+ * EntityManagerPlusOperations and PersistenceUnitInfo from
+ * @return an application-managed EntityManager that can join transactions
+ * but does not participate in them automatically
+ */
+ public static EntityManager createApplicationManagedEntityManager(
+ EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo) {
+
+ return createProxy(rawEntityManager, emfInfo, false);
+ }
+
+
+ /**
+ * Create an EntityManager that automatically joins transactions on each
+ * operation in a transaction.
+ * @param rawEntityManager raw EntityManager
+ * @param plusOperations an implementation of the EntityManagerPlusOperations
+ * interface, if those operations should be exposed (may be null)
+ * @return a container-managed EntityManager that will automatically participate
+ * in any managed transaction
+ */
+ public static EntityManager createContainerManagedEntityManager(
+ EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations) {
+
+ return createProxy(rawEntityManager, null, null, plusOperations, null, null, true);
+ }
+
+ /**
+ * Create an EntityManager that automatically joins transactions on each
+ * operation in a transaction.
+ * @param rawEntityManager raw EntityManager
+ * @param plusOperations an implementation of the EntityManagerPlusOperations
+ * interface, if those operations should be exposed (may be null)
+ * @param exceptionTranslator the exception translator to use for translating
+ * JPA commit/rollback exceptions during transaction synchronization
+ * (may be null)
+ * @return a container-managed EntityManager that will automatically participate
+ * in any managed transaction
+ */
+ public static EntityManager createContainerManagedEntityManager(
+ EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations,
+ PersistenceExceptionTranslator exceptionTranslator) {
+
+ return createProxy(rawEntityManager, null, null, plusOperations, exceptionTranslator, null, true);
+ }
+
+ /**
+ * Create an EntityManager that automatically joins transactions on each
+ * operation in a transaction.
+ * @param rawEntityManager raw EntityManager
+ * @param emfInfo the EntityManagerFactoryInfo to obtain the
+ * EntityManagerPlusOperations and PersistenceUnitInfo from
+ * @return a container-managed EntityManager that will automatically participate
+ * in any managed transaction
+ */
+ public static EntityManager createContainerManagedEntityManager(
+ EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo) {
+
+ return createProxy(rawEntityManager, emfInfo, true);
+ }
+
+
+ /**
+ * Create an EntityManager that automatically joins transactions on each
+ * operation in a transaction.
+ * @param emf the EntityManagerFactory to create the EntityManager with.
+ * If this implements the EntityManagerFactoryInfo interface, appropriate handling
+ * of the native EntityManagerFactory and available EntityManagerPlusOperations
+ * will automatically apply.
+ * @return a container-managed EntityManager that will automatically participate
+ * in any managed transaction
+ * @see javax.persistence.EntityManagerFactory#createEntityManager()
+ */
+ public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf) {
+ return createContainerManagedEntityManager(emf, null);
+ }
+
+ /**
+ * Create an EntityManager that automatically joins transactions on each
+ * operation in a transaction.
+ * @param emf the EntityManagerFactory to create the EntityManager with.
+ * If this implements the EntityManagerFactoryInfo interface, appropriate handling
+ * of the native EntityManagerFactory and available EntityManagerPlusOperations
+ * will automatically apply.
+ * @param properties the properties to be passed into the createEntityManager
+ * call (may be null)
+ * @return a container-managed EntityManager that will automatically participate
+ * in any managed transaction
+ * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
+ */
+ public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf, Map properties) {
+ Assert.notNull(emf, "EntityManagerFactory must not be null");
+ if (emf instanceof EntityManagerFactoryInfo) {
+ EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
+ EntityManagerFactory nativeEmf = emfInfo.getNativeEntityManagerFactory();
+ EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
+ nativeEmf.createEntityManager(properties) : nativeEmf.createEntityManager());
+ return createProxy(rawEntityManager, emfInfo, true);
+ }
+ else {
+ EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
+ emf.createEntityManager(properties) : emf.createEntityManager());
+ return createProxy(rawEntityManager, null, null, null, null, null, true);
+ }
+ }
+
+
+ /**
+ * Actually create the EntityManager proxy.
+ * @param rawEntityManager raw EntityManager
+ * @param emfInfo the EntityManagerFactoryInfo to obtain the
+ * EntityManagerPlusOperations and PersistenceUnitInfo from
+ * @param containerManaged whether to follow container-managed EntityManager
+ * or application-managed EntityManager semantics
+ * @return the EntityManager proxy
+ */
+ private static EntityManager createProxy(
+ EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo, boolean containerManaged) {
+
+ Assert.notNull(emfInfo, "EntityManagerFactoryInfo must not be null");
+ JpaDialect jpaDialect = emfInfo.getJpaDialect();
+ EntityManagerPlusOperations plusOperations = null;
+ if (jpaDialect != null && jpaDialect.supportsEntityManagerPlusOperations()) {
+ plusOperations = jpaDialect.getEntityManagerPlusOperations(rawEntityManager);
+ }
+ PersistenceUnitInfo pui = emfInfo.getPersistenceUnitInfo();
+ Boolean jta = (pui != null ? pui.getTransactionType() == PersistenceUnitTransactionType.JTA : null);
+ return createProxy(rawEntityManager, emfInfo.getEntityManagerInterface(),
+ emfInfo.getBeanClassLoader(), plusOperations, jpaDialect, jta, containerManaged);
+ }
+
+ /**
+ * Actually create the EntityManager proxy.
+ * @param rawEm raw EntityManager
+ * @param emIfc the (potentially vendor-specific) EntityManager
+ * interface to proxy, or null for default detection of all interfaces
+ * @param plusOperations an implementation of the EntityManagerPlusOperations
+ * interface, if those operations should be exposed (may be null)
+ * @param exceptionTranslator the PersistenceException translator to use
+ * @param jta whether to create a JTA-aware EntityManager
+ * (or null if not known in advance)
+ * @param containerManaged whether to follow container-managed EntityManager
+ * or application-managed EntityManager semantics
+ * @return the EntityManager proxy
+ */
+ private static EntityManager createProxy(
+ EntityManager rawEm, Class extends EntityManager> emIfc, ClassLoader cl,
+ EntityManagerPlusOperations plusOperations, PersistenceExceptionTranslator exceptionTranslator,
+ Boolean jta, boolean containerManaged) {
+
+ Assert.notNull(rawEm, "EntityManager must not be null");
+ Set
+ *
+ */
+ public void setFlushEager(boolean flushEager) {
+ this.flushEager = flushEager;
+ }
+
+ /**
+ * Return if this accessor should flush changes to the database eagerly.
+ */
+ public boolean isFlushEager() {
+ return this.flushEager;
+ }
+
+ /**
+ * Eagerly initialize the JPA dialect, creating a default one
+ * for the specified EntityManagerFactory if none set.
+ */
+ public void afterPropertiesSet() {
+ EntityManagerFactory emf = getEntityManagerFactory();
+ if (emf == null && getEntityManager() == null) {
+ throw new IllegalArgumentException("entityManagerFactory or entityManager is required");
+ }
+ if (emf instanceof EntityManagerFactoryInfo) {
+ JpaDialect jpaDialect = ((EntityManagerFactoryInfo) emf).getJpaDialect();
+ if (jpaDialect != null) {
+ setJpaDialect(jpaDialect);
+ }
+ }
+ }
+
+
+ /**
+ * Flush the given JPA entity manager if necessary.
+ * @param em the current JPA PersistenceManage
+ * @param existingTransaction if executing within an existing transaction
+ * @throws javax.persistence.PersistenceException in case of JPA flushing errors
+ */
+ protected void flushIfNecessary(EntityManager em, boolean existingTransaction) throws PersistenceException {
+ if (isFlushEager()) {
+ logger.debug("Eagerly flushing JPA entity manager");
+ em.flush();
+ }
+ }
+
+ /**
+ * Convert the given runtime exception to an appropriate exception from the
+ * org.springframework.dao hierarchy if necessary, or
+ * return the exception itself if it is not persistence related
+ * EntityManager.find/merge
+ * to perform some operations on persistent objects.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see org.springframework.orm.jpa.JpaTemplate
+ * @see org.springframework.orm.jpa.JpaTransactionManager
+ */
+public interface JpaCallback {
+
+ /**
+ * Gets called by JpaTemplate.execute with an active
+ * JPA EntityManager. Does not need to care about activating
+ * or closing the EntityManager, or handling transactions.
+ *
+ * null if none
+ * @throws PersistenceException if thrown by the JPA API
+ * @see org.springframework.orm.jpa.JpaTemplate#execute
+ * @see org.springframework.orm.jpa.JpaTemplate#executeFind
+ */
+ Object doInJpa(EntityManager em) throws PersistenceException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java
new file mode 100644
index 00000000000..b3802f05591
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2002-2007 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.jpa;
+
+import java.sql.SQLException;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.jdbc.datasource.ConnectionHandle;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+
+/**
+ * SPI strategy that encapsulates certain functionality that standard JPA 1.0
+ * does not offer, such as access to the underlying JDBC Connection. This
+ * strategy is mainly intended for standalone usage of a JPA provider; most
+ * of its functionality is not relevant when running with JTA transactions.
+ *
+ * begin, or invoke a special begin method that takes,
+ * for example, an isolation level.
+ * cleanupTransaction.
+ * It may also apply the read-only flag and isolation level to the underlying
+ * JDBC Connection before beginning the transaction.
+ * cleanupTransaction.
+ * releaseJdbcConnection method when not needed anymore.
+ * releaseJdbcConnection,
+ * an implementation should use a special handle that references that other object.
+ * @param entityManager the current JPA EntityManager
+ * @param readOnly whether the Connection is only needed for read-only purposes
+ * @return a handle for the JDBC Connection, to be passed into
+ * releaseJdbcConnection, or null
+ * if no JDBC Connection can be retrieved
+ * @throws javax.persistence.PersistenceException if thrown by JPA methods
+ * @throws java.sql.SQLException if thrown by JDBC methods
+ * @see #releaseJdbcConnection
+ * @see org.springframework.jdbc.datasource.ConnectionHandle#getConnection
+ * @see org.springframework.jdbc.datasource.SimpleConnectionHandle
+ * @see JpaTransactionManager#setDataSource
+ * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
+ */
+ ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly)
+ throws PersistenceException, SQLException;
+
+ /**
+ * Release the given JDBC Connection, which has originally been retrieved
+ * via getJdbcConnection. This should be invoked in any case,
+ * to allow for proper release of the retrieved Connection handle.
+ * getJdbcConnection will be implicitly closed when the JPA
+ * transaction completes or when the EntityManager is closed.
+ * @param conHandle the JDBC Connection handle to release
+ * @param entityManager the current JPA EntityManager
+ * @throws javax.persistence.PersistenceException if thrown by JPA methods
+ * @throws java.sql.SQLException if thrown by JDBC methods
+ * @see #getJdbcConnection
+ */
+ void releaseJdbcConnection(ConnectionHandle conHandle, EntityManager entityManager)
+ throws PersistenceException, SQLException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaInterceptor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaInterceptor.java
new file mode 100644
index 00000000000..ea68dcfc8f9
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaInterceptor.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import javax.persistence.EntityManager;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * This interceptor binds a new JPA EntityManager to the thread before a method
+ * call, closing and removing it afterwards in case of any method outcome.
+ * If there already is a pre-bound EntityManager (e.g. from JpaTransactionManager,
+ * or from a surrounding JPA-intercepted method), the interceptor simply participates in it.
+ *
+ * EntityManagerFactoryUtils.getEntityManager method or - preferably -
+ * via a shared EntityManager reference, to be able to detect a
+ * thread-bound EntityManager. Typically, the code will look like as follows:
+ *
+ *
+ * public void doSomeDataAccessAction() {
+ * this.entityManager...
+ * }
+ *
+ * EntityManagerFactoryUtils.convertJpaAccessException
+ * method that converts them to exceptions that are compatible with the
+ * org.springframework.dao exception hierarchy (like JpaTemplate does).
+ *
+ *
+ *
+ *
+ * org.springframework.dao exception hierarchy.
+ * JpaTemplate's data access methods that mirror
+ * various {@link javax.persistence.EntityManager} methods. Users are
+ * strongly encouraged to read the JPA EntityManager
+ * javadocs for details on the semantics of those methods.
+ *
+ * EntityManager, either within a managed transaction or within
+ * {@link org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter}/
+ * {@link org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor}.
+ * Furthermore, some operations just make sense within transactions,
+ * for example: flush, clear.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see JpaTemplate
+ * @see javax.persistence.EntityManager
+ * @see JpaTransactionManager
+ * @see JpaDialect
+ * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
+ * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
+ */
+public interface JpaOperations {
+
+ Object execute(JpaCallback action) throws DataAccessException;
+
+ List executeFind(JpaCallback action) throws DataAccessException;
+
+ org.springframework.dao exceptions.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
+ */
+public class JpaSystemException extends UncategorizedDataAccessException {
+
+ public JpaSystemException(PersistenceException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTemplate.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTemplate.java
new file mode 100644
index 00000000000..acd36a44848
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTemplate.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+import javax.persistence.Query;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Helper class that allows for writing JPA data access code in the same style
+ * as with Spring's well-known JdoTemplate and HibernateTemplate classes.
+ * Automatically converts PersistenceExceptions into Spring DataAccessExceptions,
+ * following the org.springframework.dao exception hierarchy.
+ *
+ * close calls and automatically applying transaction
+ * timeouts (if any).
+ * null
+ * @throws org.springframework.dao.DataAccessException in case of JPA errors
+ */
+ public Object execute(JpaCallback action, boolean exposeNativeEntityManager) throws DataAccessException {
+ Assert.notNull(action, "Callback object must not be null");
+
+ EntityManager em = getEntityManager();
+ boolean isNewEm = false;
+ if (em == null) {
+ em = getTransactionalEntityManager();
+ if (em == null) {
+ logger.debug("Creating new EntityManager for JpaTemplate execution");
+ em = createEntityManager();
+ isNewEm = true;
+ }
+ }
+
+ try {
+ EntityManager emToExpose = (exposeNativeEntityManager ? em : createEntityManagerProxy(em));
+ Object result = action.doInJpa(emToExpose);
+ flushIfNecessary(em, !isNewEm);
+ return result;
+ }
+ catch (RuntimeException ex) {
+ throw translateIfNecessary(ex);
+ }
+ finally {
+ if (isNewEm) {
+ logger.debug("Closing new EntityManager after JPA template execution");
+ EntityManagerFactoryUtils.closeEntityManager(em);
+ }
+ }
+ }
+
+ /**
+ * Create a close-suppressing proxy for the given JPA EntityManager.
+ * The proxy also prepares returned JPA Query objects.
+ * @param em the JPA EntityManager to create a proxy for
+ * @return the EntityManager proxy, implementing all interfaces
+ * implemented by the passed-in EntityManager object (that is,
+ * also implementing all provider-specific extension interfaces)
+ * @see javax.persistence.EntityManager#close
+ */
+ protected EntityManager createEntityManagerProxy(EntityManager em) {
+ Class[] ifcs = null;
+ EntityManagerFactory emf = getEntityManagerFactory();
+ if (emf instanceof EntityManagerFactoryInfo) {
+ Class entityManagerInterface = ((EntityManagerFactoryInfo) emf).getEntityManagerInterface();
+ if (entityManagerInterface != null) {
+ ifcs = new Class[] {entityManagerInterface};
+ }
+ }
+ if (ifcs == null) {
+ ifcs = ClassUtils.getAllInterfacesForClass(em.getClass());
+ }
+ return (EntityManager) Proxy.newProxyInstance(
+ em.getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(em));
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for load, save, delete
+ //-------------------------------------------------------------------------
+
+ @SuppressWarnings("unchecked")
+ public EntityManagerFactory.createEntityManager(Map) (if any).
+ * EntityManagerFactory.createEntityManager(Map) (if any).
+ * doSuspend and doResume.
+ */
+ private static class SuspendedResourcesHolder {
+
+ private final EntityManagerHolder entityManagerHolder;
+
+ private final ConnectionHolder connectionHolder;
+
+ private SuspendedResourcesHolder(EntityManagerHolder emHolder, ConnectionHolder conHolder) {
+ this.entityManagerHolder = emHolder;
+ this.connectionHolder = conHolder;
+ }
+
+ private EntityManagerHolder getEntityManagerHolder() {
+ return this.entityManagerHolder;
+ }
+
+ private ConnectionHolder getConnectionHolder() {
+ return this.connectionHolder;
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java
new file mode 100644
index 00000000000..52598a1141d
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa;
+
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.spi.PersistenceProvider;
+
+/**
+ * SPI interface that allows to plug in vendor-specific behavior
+ * into Spring's EntityManagerFactory creators. Serves as single
+ * configuration point for all vendor-specific properties.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.0
+ * @see AbstractEntityManagerFactoryBean#setJpaVendorAdapter
+ */
+public interface JpaVendorAdapter {
+
+ /**
+ * Return the vendor-specific persistence provider.
+ */
+ PersistenceProvider getPersistenceProvider();
+
+ /**
+ * Return the name of the persistence provider's root package
+ * (e.g. "oracle.toplink.essentials"). Will be used for
+ * excluding provider classes from temporary class overriding.
+ * @since 2.5.2
+ */
+ String getPersistenceProviderRootPackage();
+
+ /**
+ * Return a Map of vendor-specific JPA properties,
+ * typically based on settings in this JpaVendorAdapter instance.
+ * null or an empty Map
+ * if there are no such properties to expose
+ * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
+ * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
+ */
+ Map getJpaPropertyMap();
+
+ /**
+ * Return the vendor-specific JpaDialect implementation for this
+ * provider, or null if there is none.
+ */
+ JpaDialect getJpaDialect();
+
+ /**
+ * Return the vendor-specific EntityManagerFactory interface
+ * that the EntityManagerFactory proxy is supposed to implement.
+ * META-INF/persistence.xml config file,
+ * residing in the class path, according to the general JPA configuration contract.
+ * However, this FactoryBean is more flexible in that you can override the location
+ * of the persistence.xml file, specify the JDBC DataSources to link to,
+ * etc. Furthermore, it allows for pluggable class instrumentation through Spring's
+ * {@link org.springframework.instrument.classloading.LoadTimeWeaver} abstraction,
+ * instead of being tied to a special VM agent specified on JVM startup.
+ *
+ * persistence.xml file
+ * itself and creates a corresponding {@link javax.persistence.spi.PersistenceUnitInfo}
+ * object (with further configuration merged in, such as JDBC DataSources and the
+ * Spring LoadTimeWeaver), to be passed to the chosen JPA
+ * {@link javax.persistence.spi.PersistenceProvider}. This corresponds to a
+ * local JPA container with full support for the standard JPA container contract.
+ *
+ * persistence.xml location, DataSource
+ * configuration and LoadTimeWeaver will be defined on that separate
+ * DefaultPersistenceUnitManager bean in such a scenario.
+ * @see #setPersistenceXmlLocation
+ * @see #setDataSource
+ * @see #setLoadTimeWeaver
+ * @see org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager
+ */
+ public void setPersistenceUnitManager(PersistenceUnitManager persistenceUnitManager) {
+ this.persistenceUnitManager = persistenceUnitManager;
+ }
+
+ /**
+ * Set the location of the persistence.xml file
+ * we want to use. This is a Spring resource location.
+ * persistence.xml file
+ * that this LocalContainerEntityManagerFactoryBean should parse
+ * @see #setPersistenceUnitManager
+ */
+ public void setPersistenceXmlLocation(String persistenceXmlLocation) {
+ this.internalPersistenceUnitManager.setPersistenceXmlLocations(new String[] {persistenceXmlLocation});
+ }
+
+ /**
+ * Specify the JDBC DataSource that the JPA persistence provider is supposed
+ * to use for accessing the database. This is an alternative to keeping the
+ * JDBC configuration in persistence.xml, passing in a Spring-managed
+ * DataSource instead.
+ * persistence.xml (if any).
+ * persistence.xml.
+ * context:load-time-weaver XML tag for creating
+ * such a shared LoadTimeWeaver (autodetecting the environment by default).
+ * persistence.xml, as defined in the JPA specification.
+ * If no entity manager name was specified, it takes the first info in the
+ * array as returned by the reader. Otherwise, it checks for a matching name.
+ * @param persistenceUnitManager the PersistenceUnitManager to obtain from
+ * @return the chosen PersistenceUnitInfo
+ */
+ protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) {
+ if (getPersistenceUnitName() != null) {
+ return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName());
+ }
+ else {
+ return persistenceUnitManager.obtainDefaultPersistenceUnitInfo();
+ }
+ }
+
+ /**
+ * Hook method allowing subclasses to customize the EntityManagerFactory
+ * after its creation via the PersistenceProvider.
+ * META-INF/persistence.xml
+ * config file, residing in the class path, according to the JPA standalone bootstrap
+ * contract. Additionally, most JPA providers will require a special VM agent
+ * (specified on JVM startup) that allows them to instrument application classes.
+ * See the Java Persistence API specification and your provider documentation
+ * for setup details.
+ *
+ * createEntityManager call (may be null)
+ * @return a shareable transaction EntityManager proxy
+ */
+ public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map properties) {
+ Class[] emIfcs = null;
+ if (emf instanceof EntityManagerFactoryInfo) {
+ EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
+ Class emIfc = emfInfo.getEntityManagerInterface();
+ if (emIfc == null) {
+ emIfc = EntityManager.class;
+ }
+ JpaDialect jpaDialect = emfInfo.getJpaDialect();
+ if (jpaDialect != null && jpaDialect.supportsEntityManagerPlusOperations()) {
+ emIfcs = new Class[] {emIfc, EntityManagerPlus.class};
+ }
+ else {
+ emIfcs = new Class[] {emIfc};
+ }
+ }
+ else {
+ emIfcs = new Class[] {EntityManager.class};
+ }
+ return createSharedEntityManager(emf, properties, emIfcs);
+ }
+
+ /**
+ * Create a transactional EntityManager proxy for the given EntityManagerFactory.
+ * @param emf EntityManagerFactory to obtain EntityManagers from as needed
+ * @param properties the properties to be passed into the
+ * createEntityManager call (may be null)
+ * @param entityManagerInterfaces the interfaces to be implemented by the
+ * EntityManager. Allows the addition or specification of proprietary interfaces.
+ * @return a shareable transactional EntityManager proxy
+ */
+ public static EntityManager createSharedEntityManager(
+ EntityManagerFactory emf, Map properties, Class... entityManagerInterfaces) {
+
+ ClassLoader cl = null;
+ if (emf instanceof EntityManagerFactoryInfo) {
+ cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader();
+ }
+ Class[] ifcs = new Class[entityManagerInterfaces.length + 1];
+ System.arraycopy(entityManagerInterfaces, 0, ifcs, 0, entityManagerInterfaces.length);
+ ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class;
+ return (EntityManager) Proxy.newProxyInstance(
+ (cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),
+ ifcs, new SharedEntityManagerInvocationHandler(emf, properties));
+ }
+
+
+ /**
+ * Invocation handler that delegates all calls to the current
+ * transactional EntityManager, if any; else, it will fall back
+ * to a newly created EntityManager per operation.
+ */
+ private static class SharedEntityManagerInvocationHandler implements InvocationHandler {
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final EntityManagerFactory targetFactory;
+
+ private final Map properties;
+
+ public SharedEntityManagerInvocationHandler(EntityManagerFactory target, Map properties) {
+ this.targetFactory = target;
+ this.properties = properties;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Invocation on EntityManager interface coming in...
+
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0]);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of EntityManager proxy.
+ return hashCode();
+ }
+ else if (method.getName().equals("toString")) {
+ // Deliver toString without touching a target EntityManager.
+ return "Shared EntityManager proxy for target factory [" + this.targetFactory + "]";
+ }
+ else if (method.getName().equals("isOpen")) {
+ // Handle isOpen method: always return true.
+ return true;
+ }
+ else if (method.getName().equals("close")) {
+ // Handle close method: suppress, not valid.
+ return null;
+ }
+ else if (method.getName().equals("getTransaction")) {
+ throw new IllegalStateException(
+ "Not allowed to create transaction on shared EntityManager - " +
+ "use Spring transactions or EJB CMT instead");
+ }
+ else if (method.getName().equals("joinTransaction")) {
+ throw new IllegalStateException(
+ "Not allowed to join transaction on shared EntityManager - " +
+ "use Spring transactions or EJB CMT instead");
+ }
+
+ // Determine current EntityManager: either the transactional one
+ // managed by the factory or a temporary one for the given invocation.
+ EntityManager target =
+ EntityManagerFactoryUtils.doGetTransactionalEntityManager(this.targetFactory, this.properties);
+
+ // Handle EntityManagerProxy interface.
+ if (method.getName().equals("getTargetEntityManager")) {
+ if (target == null) {
+ throw new IllegalStateException("No transactional EntityManager available");
+ }
+ return target;
+ }
+
+ // Regular EntityManager operations.
+ boolean isNewEm = false;
+ if (target == null) {
+ logger.debug("Creating new EntityManager for shared EntityManager invocation");
+ target = (!CollectionUtils.isEmpty(this.properties) ?
+ this.targetFactory.createEntityManager(this.properties) :
+ this.targetFactory.createEntityManager());
+ isNewEm = true;
+ }
+
+ // Invoke method on current EntityManager.
+ try {
+ Object result = method.invoke(target, args);
+ if (isNewEm && result instanceof Query) {
+ result = Proxy.newProxyInstance(Query.class.getClassLoader(), new Class[] {Query.class},
+ new DeferredQueryInvocationHandler((Query) result, target));
+ isNewEm = false;
+ }
+ return result;
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ finally {
+ if (isNewEm) {
+ EntityManagerFactoryUtils.closeEntityManager(target);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Invocation handler that handles deferred Query objects created by
+ * non-transactional createQuery invocations on a shared EntityManager.
+ */
+ private static class DeferredQueryInvocationHandler implements InvocationHandler {
+
+ private final Query target;
+
+ private final EntityManager em;
+
+ private DeferredQueryInvocationHandler(Query target, EntityManager em) {
+ this.target = target;
+ this.em = em;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Invocation on Query interface coming in...
+
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0]);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of EntityManager proxy.
+ return hashCode();
+ }
+
+ // Invoke method on actual Query object.
+ try {
+ return method.invoke(this.target, args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ finally {
+ if (method.getName().equals("getResultList") || method.getName().equals("getSingleResult") ||
+ method.getName().equals("executeUpdate")) {
+ EntityManagerFactoryUtils.closeEntityManager(this.em);
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/package.html b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/package.html
new file mode 100644
index 00000000000..a223af08b2c
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/package.html
@@ -0,0 +1,9 @@
+
+java.lang.instrument.ClassFileTransformer
+ * interface based on a JPA ClassTransformer which a JPA PersistenceProvider asks the
+ * PersistenceUnitInfo to install in the current runtime.
+ *
+ * @author Rod Johnson
+ * @since 2.0
+ * @see javax.persistence.spi.PersistenceUnitInfo#addTransformer(javax.persistence.spi.ClassTransformer)
+ */
+class ClassFileTransformerAdapter implements ClassFileTransformer {
+
+ private static final Log logger = LogFactory.getLog(ClassFileTransformerAdapter.class);
+
+ private final ClassTransformer classTransformer;
+
+
+ public ClassFileTransformerAdapter(ClassTransformer classTransformer) {
+ Assert.notNull(classTransformer, "ClassTransformer must not be null");
+ this.classTransformer = classTransformer;
+ }
+
+
+ public byte[] transform(
+ ClassLoader loader, String className, Class> classBeingRedefined,
+ ProtectionDomain protectionDomain, byte[] classfileBuffer) {
+
+ try {
+ byte[] transformed = this.classTransformer.transform(
+ loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
+ if (transformed != null && logger.isDebugEnabled()) {
+ logger.debug("Transformer of class [" + this.classTransformer.getClass().getName() +
+ "] transformed class [" + className + "]; bytes in=" +
+ classfileBuffer.length + "; bytes out=" + transformed.length);
+ }
+ return transformed;
+ }
+ catch (ClassCircularityError ex) {
+ logger.error("Error weaving class [" + className + "] with " +
+ "transformer of class [" + this.classTransformer.getClass().getName() + "]", ex);
+ throw new IllegalStateException("Could not weave class [" + className + "]", ex);
+ }
+ catch (Throwable ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Error weaving class [" + className + "] with " +
+ "transformer of class [" + this.classTransformer.getClass().getName() + "]", ex);
+ }
+ // The exception will be ignored by the class loader, anyway...
+ throw new IllegalStateException("Could not weave class [" + className + "]", ex);
+ }
+ }
+
+
+ @Override
+ public String toString() {
+ return "Standard ClassFileTransformer wrapping JPA transformer: " + this.classTransformer;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
new file mode 100644
index 00000000000..25b5a7f1e07
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa.persistenceunit;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.PersistenceException;
+import javax.persistence.spi.PersistenceUnitInfo;
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.context.weaving.LoadTimeWeaverAware;
+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.instrument.classloading.InstrumentationLoadTimeWeaver;
+import org.springframework.instrument.classloading.LoadTimeWeaver;
+import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
+import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
+import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Default implementation of the {@link PersistenceUnitManager} interface.
+ * Used as internal default by
+ * {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean}.
+ *
+ * persistence.xml files,
+ * with configurable file locations, JDBC DataSource lookup and load-time weaving.
+ *
+ * classpath*:META-INF/persistence.xml,
+ * scanning for all matching files in the class path (as defined in the JPA specification).
+ * DataSource names are by default interpreted as JNDI names, and no load time weaving
+ * is available (which requires weaving to be turned off in the persistence provider).
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setPersistenceXmlLocations
+ * @see #setDataSourceLookup
+ * @see #setLoadTimeWeaver
+ * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setPersistenceUnitManager
+ */
+public class DefaultPersistenceUnitManager
+ implements PersistenceUnitManager, ResourceLoaderAware, LoadTimeWeaverAware, InitializingBean {
+
+ /**
+ * Default location of the persistence.xml file:
+ * "classpath*:META-INF/persistence.xml".
+ */
+ public final static String DEFAULT_PERSISTENCE_XML_LOCATION = "classpath*:META-INF/persistence.xml";
+
+ /**
+ * Default location for the persistence unit root URL:
+ * "classpath:", indicating the root of the class path.
+ */
+ public final static String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION = "classpath:";
+
+
+ /** Location of persistence.xml file(s) */
+ private String[] persistenceXmlLocations = new String[] {DEFAULT_PERSISTENCE_XML_LOCATION};
+
+ private String defaultPersistenceUnitRootLocation = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION;
+
+ private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
+
+ private DataSource defaultDataSource;
+
+ private PersistenceUnitPostProcessor[] persistenceUnitPostProcessors;
+
+ private LoadTimeWeaver loadTimeWeaver;
+
+ private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+
+ private final Setpersistence.xml files to load.
+ * These can be specified as Spring resource locations and/or location patterns.
+ * persistence.xml files to load.
+ * These can be specified as Spring resource locations and/or location patterns.
+ * persistence.xml files to read
+ */
+ public void setPersistenceXmlLocations(String[] persistenceXmlLocations) {
+ this.persistenceXmlLocations = persistenceXmlLocations;
+ }
+
+ /**
+ * Set the default persistence unit root location, to be applied
+ * if no unit-specific persistence unit root could be determined.
+ * persistence.xml against Spring-managed DataSources.
+ * persistence.xml.
+ * If not specified, data source names will be resolved as JNDI names instead
+ * (as defined by standard JPA).
+ * @see org.springframework.jdbc.datasource.lookup.MapDataSourceLookup
+ */
+ public void setDataSources(Mappersistence.xml
+ * against Spring-managed DataSource instances.
+ * persistence.xml file
+ * does not define DataSource names at all, specify a default DataSource
+ * via the "defaultDataSource" property.
+ * @see org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup
+ * @see org.springframework.jdbc.datasource.lookup.BeanFactoryDataSourceLookup
+ * @see #setDataSources
+ * @see #setDefaultDataSource
+ */
+ public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
+ this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
+ }
+
+ /**
+ * Return the JDBC DataSourceLookup that provides DataSources for the
+ * persistence provider, resolving data source names in persistence.xml
+ * against Spring-managed DataSource instances.
+ */
+ public DataSourceLookup getDataSourceLookup() {
+ return this.dataSourceLookup;
+ }
+
+ /**
+ * Specify the JDBC DataSource that the JPA persistence provider is supposed
+ * to use for accessing the database if none has been specified in
+ * persistence.xml.
+ * persistence.xml.
+ */
+ public DataSource getDefaultDataSource() {
+ return this.defaultDataSource;
+ }
+
+ /**
+ * Set the PersistenceUnitPostProcessors to be applied to each
+ * PersistenceUnitInfo that has been parsed by this manager.
+ * persistence.xml.
+ */
+ public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor[] postProcessors) {
+ this.persistenceUnitPostProcessors = postProcessors;
+ }
+
+ /**
+ * Return the PersistenceUnitPostProcessors to be applied to each
+ * PersistenceUnitInfo that has been parsed by this manager.
+ */
+ public PersistenceUnitPostProcessor[] getPersistenceUnitPostProcessors() {
+ return this.persistenceUnitPostProcessors;
+ }
+
+ /**
+ * Specify the Spring LoadTimeWeaver to use for class instrumentation according
+ * to the JPA class transformer contract.
+ * context:load-time-weaver XML tag for creating
+ * such a shared LoadTimeWeaver (autodetecting the environment by default).
+ * @see org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver
+ * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
+ * @see org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader
+ */
+ public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
+ this.loadTimeWeaver = loadTimeWeaver;
+ }
+
+ /**
+ * Return the Spring LoadTimeWeaver to use for class instrumentation according
+ * to the JPA class transformer contract.
+ */
+ public LoadTimeWeaver getLoadTimeWeaver() {
+ return this.loadTimeWeaver;
+ }
+
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourcePatternResolver = (resourceLoader != null ?
+ ResourcePatternUtils.getResourcePatternResolver(resourceLoader) :
+ new PathMatchingResourcePatternResolver());
+ }
+
+
+ public void afterPropertiesSet() {
+ if (this.loadTimeWeaver == null && InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
+ this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(this.resourcePatternResolver.getClassLoader());
+ }
+ preparePersistenceUnitInfos();
+ }
+
+ /**
+ * Prepare the PersistenceUnitInfos according to the configuration
+ * of this manager: scanning for persistence.xml files,
+ * parsing all matching files, configurating and post-processing them.
+ * persistence.xml,
+ * as defined in the JPA specification.
+ */
+ private SpringPersistenceUnitInfo[] readPersistenceUnitInfos() {
+ PersistenceUnitReader reader = new PersistenceUnitReader(this.resourcePatternResolver, this.dataSourceLookup);
+ return reader.readPersistenceUnitInfos(this.persistenceXmlLocations);
+ }
+
+ /**
+ * Try to determine the persistence unit root URL based on the given
+ * "defaultPersistenceUnitRootLocation".
+ * @return the persistence unit root URL to pass to the JPA PersistenceProvider
+ * @see #setDefaultPersistenceUnitRootLocation
+ */
+ private URL determineDefaultPersistenceUnitRootUrl() {
+ if (this.defaultPersistenceUnitRootLocation == null) {
+ return null;
+ }
+ try {
+ Resource res = this.resourcePatternResolver.getResource(this.defaultPersistenceUnitRootLocation);
+ return res.getURL();
+ }
+ catch (IOException ex) {
+ throw new PersistenceException("Unable to resolve persistence unit root URL", ex);
+ }
+ }
+
+ /**
+ * Return the specified PersistenceUnitInfo from this manager's cache
+ * of processed persistence units, keeping it in the cache (i.e. not
+ * 'obtaining' it for use but rather just accessing it for post-processing).
+ * null if not available
+ */
+ protected final MutablePersistenceUnitInfo getPersistenceUnitInfo(String persistenceUnitName) {
+ return this.persistenceUnitInfos.get(persistenceUnitName);
+ }
+
+ /**
+ * Hook method allowing subclasses to customize each PersistenceUnitInfo.
+ * persistence.xml.
+ * Passed in as MutablePersistenceUnitInfo.
+ * @see #setPersistenceUnitPostProcessors
+ */
+ protected void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
+ PersistenceUnitPostProcessor[] postProcessors = getPersistenceUnitPostProcessors();
+ if (postProcessors != null) {
+ for (PersistenceUnitPostProcessor postProcessor : postProcessors) {
+ postProcessor.postProcessPersistenceUnitInfo(pui);
+ }
+ }
+ }
+
+
+ public PersistenceUnitInfo obtainDefaultPersistenceUnitInfo() {
+ if (this.persistenceUnitInfoNames.isEmpty()) {
+ throw new IllegalStateException("No persistence units parsed from " +
+ ObjectUtils.nullSafeToString(this.persistenceXmlLocations));
+ }
+ if (this.persistenceUnitInfos.isEmpty()) {
+ throw new IllegalStateException("All persistence units from " +
+ ObjectUtils.nullSafeToString(this.persistenceXmlLocations) + " already obtained");
+ }
+ if (this.persistenceUnitInfos.size() > 1) {
+ throw new IllegalStateException("No single default persistence unit defined in " +
+ ObjectUtils.nullSafeToString(this.persistenceXmlLocations));
+ }
+ PersistenceUnitInfo pui = this.persistenceUnitInfos.values().iterator().next();
+ this.persistenceUnitInfos.clear();
+ return pui;
+ }
+
+ public PersistenceUnitInfo obtainPersistenceUnitInfo(String persistenceUnitName) {
+ PersistenceUnitInfo pui = this.persistenceUnitInfos.remove(persistenceUnitName);
+ if (pui == null) {
+ if (!this.persistenceUnitInfoNames.contains(persistenceUnitName)) {
+ throw new IllegalArgumentException(
+ "No persistence unit with name '" + persistenceUnitName + "' found");
+ }
+ else {
+ throw new IllegalStateException(
+ "Persistence unit with name '" + persistenceUnitName + "' already obtained");
+ }
+ }
+ return pui;
+ }
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java
new file mode 100644
index 00000000000..aeafe8aad76
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa.persistenceunit;
+
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.persistence.spi.ClassTransformer;
+import javax.persistence.spi.PersistenceUnitInfo;
+import javax.persistence.spi.PersistenceUnitTransactionType;
+import javax.sql.DataSource;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * Spring's base implementation of the JPA
+ * {@link javax.persistence.spi.PersistenceUnitInfo} interface,
+ * used to bootstrap an EntityManagerFactory in a container.
+ *
+ * null)
+ * @throws IllegalStateException if there is no default PersistenceUnitInfo defined
+ * or it has already been obtained
+ */
+ PersistenceUnitInfo obtainDefaultPersistenceUnitInfo() throws IllegalStateException;
+
+ /**
+ * Obtain the specified PersistenceUnitInfo from this manager.
+ * @param persistenceUnitName the name of the desired persistence unit
+ * @return the PersistenceUnitInfo (never null)
+ * @throws IllegalArgumentException if no PersistenceUnitInfo with the given
+ * name is defined
+ * @throws IllegalStateException if the PersistenceUnitInfo with the given
+ * name has already been obtained
+ */
+ PersistenceUnitInfo obtainPersistenceUnitInfo(String persistenceUnitName)
+ throws IllegalArgumentException, IllegalStateException;
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitPostProcessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitPostProcessor.java
new file mode 100644
index 00000000000..42f0aa6be6c
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitPostProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2006 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.jpa.persistenceunit;
+
+/**
+ * Callback interface for post-processing a JPA PersistenceUnitInfo.
+ * Implementations can be registered with a DefaultPersistenceUnitManager
+ * or via a LocalContainerEntityManagerFactoryBean.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see DefaultPersistenceUnitManager#setPersistenceUnitPostProcessors
+ * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setPersistenceUnitPostProcessors
+ */
+public interface PersistenceUnitPostProcessor {
+
+ /**
+ * Post-process the given PersistenceUnitInfo, for example registering
+ * further entity classes and jar files.
+ * @param pui the chosen PersistenceUnitInfo, as read from persistence.xml.
+ * Passed in as MutablePersistenceUnitInfo.
+ */
+ void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui);
+
+}
diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
new file mode 100644
index 00000000000..19232c585a6
--- /dev/null
+++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.orm.jpa.persistenceunit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.persistence.spi.PersistenceUnitTransactionType;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.springframework.util.xml.SimpleSaxErrorHandler;
+
+/**
+ * Internal helper class for reading persistence.xml files.
+ *
+ * @author Costin Leau
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+class PersistenceUnitReader {
+
+ private static final String MAPPING_FILE_NAME = "mapping-file";
+
+ private static final String JAR_FILE_URL = "jar-file";
+
+ private static final String MANAGED_CLASS_NAME = "class";
+
+ private static final String PROPERTIES = "properties";
+
+ private static final String PROVIDER = "provider";
+
+ private static final String EXCLUDE_UNLISTED_CLASSES = "exclude-unlisted-classes";
+
+ private static final String NON_JTA_DATA_SOURCE = "non-jta-data-source";
+
+ private static final String JTA_DATA_SOURCE = "jta-data-source";
+
+ private static final String TRANSACTION_TYPE = "transaction-type";
+
+ private static final String PERSISTENCE_UNIT = "persistence-unit";
+
+ private static final String UNIT_NAME = "name";
+
+ private static final String META_INF = "META-INF";
+
+ private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+
+ private static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
+
+ private static final String SCHEMA_NAME = "persistence_1_0.xsd";
+
+ private static final String[] SCHEMA_RESOURCE_LOCATIONS = {
+ "classpath:persistence_1_0.xsd",
+ "classpath:org/hibernate/ejb/persistence_1_0.xsd",
+ "classpath:org/jpox/jpa/persistence_1_0.xsd"};
+
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final ResourcePatternResolver resourcePatternResolver;
+
+ private final DataSourceLookup dataSourceLookup;
+
+
+ /**
+ * Create a new PersistenceUnitReader.
+ * @param resourcePatternResolver the ResourcePatternResolver to use for loading resources
+ * @param dataSourceLookup the DataSourceLookup to resolve DataSource names in
+ * persistence.xml files against
+ */
+ public PersistenceUnitReader(ResourcePatternResolver resourcePatternResolver, DataSourceLookup dataSourceLookup) {
+ Assert.notNull(resourcePatternResolver, "ResourceLoader must not be null");
+ Assert.notNull(dataSourceLookup, "DataSourceLookup must not be null");
+ this.resourcePatternResolver = resourcePatternResolver;
+ this.dataSourceLookup = dataSourceLookup;
+ }
+
+
+ /**
+ * Parse and build all persistence unit infos defined in the specified XML file(s).
+ * @param persistenceXmlLocation the resource location (can be a pattern)
+ * @return the resulting PersistenceUnitInfo instances
+ */
+ public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String persistenceXmlLocation) {
+ return readPersistenceUnitInfos(new String[] {persistenceXmlLocation});
+ }
+
+ /**
+ * Parse and build all persistence unit infos defined in the given XML files.
+ * @param persistenceXmlLocations the resource locations (can be patterns)
+ * @return the resulting PersistenceUnitInfo instances
+ */
+ public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String[] persistenceXmlLocations) {
+ ErrorHandler handler = new SimpleSaxErrorHandler(logger);
+ Listnull if none found
+ */
+ protected Resource findSchemaResource() {
+ for (int i = 0; i < SCHEMA_RESOURCE_LOCATIONS.length; i++) {
+ Resource schemaLocation = this.resourcePatternResolver.getResource(SCHEMA_RESOURCE_LOCATIONS[i]);
+ if (schemaLocation.exists()) {
+ return schemaLocation;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Parse the validated document and add entries to the given unit info list.
+ */
+ protected Listpersistence.xml file we're reading).
+ * @param resource the resource to check
+ * @return the corresponding persistence unit root URL
+ * @throws IOException if the checking failed
+ */
+ protected URL determinePersistenceUnitRootUrl(Resource resource) throws IOException {
+ URL originalURL = resource.getURL();
+ String urlToString = originalURL.toExternalForm();
+
+ // If we get an archive, simply return the jar URL (section 6.2 from the JPA spec)
+ if (ResourceUtils.isJarURL(originalURL)) {
+ return ResourceUtils.extractJarFileURL(originalURL);
+ }
+
+ else {
+ // check META-INF folder
+ if (!urlToString.contains(META_INF)) {
+ if (logger.isInfoEnabled()) {
+ logger.info(resource.getFilename() +
+ " should be located inside META-INF directory; cannot determine persistence unit root URL for " +
+ resource);
+ }
+ return null;
+ }
+ if (urlToString.lastIndexOf(META_INF) == urlToString.lastIndexOf('/') - (1 + META_INF.length())) {
+ if (logger.isInfoEnabled()) {
+ logger.info(resource.getFilename() +
+ " is not located in the root of META-INF directory; cannot determine persistence unit root URL for " +
+ resource);
+ }
+ return null;
+ }
+
+ String persistenceUnitRoot = urlToString.substring(0, urlToString.lastIndexOf(META_INF));
+ return new URL(persistenceUnitRoot);
+ }
+ }
+
+ /**
+ * Parse the unit info DOM element.
+ */
+ protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit) throws IOException {
+ SpringPersistenceUnitInfo unitInfo = new SpringPersistenceUnitInfo();
+
+ // set unit name
+ unitInfo.setPersistenceUnitName(persistenceUnit.getAttribute(UNIT_NAME).trim());
+
+ // set transaction type
+ String txType = persistenceUnit.getAttribute(TRANSACTION_TYPE).trim();
+ if (StringUtils.hasText(txType)) {
+ unitInfo.setTransactionType(PersistenceUnitTransactionType.valueOf(txType));
+ }
+
+ // data-source
+ String jtaDataSource = DomUtils.getChildElementValueByTagName(persistenceUnit, JTA_DATA_SOURCE);
+ if (StringUtils.hasText(jtaDataSource)) {
+ unitInfo.setJtaDataSource(this.dataSourceLookup.getDataSource(jtaDataSource.trim()));
+ }
+
+ String nonJtaDataSource = DomUtils.getChildElementValueByTagName(persistenceUnit, NON_JTA_DATA_SOURCE);
+ if (StringUtils.hasText(nonJtaDataSource)) {
+ unitInfo.setNonJtaDataSource(this.dataSourceLookup.getDataSource(nonJtaDataSource.trim()));
+ }
+
+ // provider
+ String provider = DomUtils.getChildElementValueByTagName(persistenceUnit, PROVIDER);
+ if (StringUtils.hasText(provider)) {
+ unitInfo.setPersistenceProviderClassName(provider.trim());
+ }
+
+ // exclude unlisted classes
+ Element excludeUnlistedClasses = DomUtils.getChildElementByTagName(persistenceUnit, EXCLUDE_UNLISTED_CLASSES);
+ if (excludeUnlistedClasses != null) {
+ unitInfo.setExcludeUnlistedClasses(true);
+ }
+
+ parseMappingFiles(persistenceUnit, unitInfo);
+ parseJarFiles(persistenceUnit, unitInfo);
+ parseClass(persistenceUnit, unitInfo);
+ parseProperty(persistenceUnit, unitInfo);
+
+ return unitInfo;
+ }
+
+ /**
+ * Parse the property XML elements.
+ */
+ @SuppressWarnings("unchecked")
+ protected void parseProperty(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
+ Element propRoot = DomUtils.getChildElementByTagName(persistenceUnit, PROPERTIES);
+ if (propRoot == null) {
+ return;
+ }
+ Listclass XML elements.
+ */
+ @SuppressWarnings("unchecked")
+ protected void parseClass(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
+ Listjar-file XML elements.
+ */
+ @SuppressWarnings("unchecked")
+ protected void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) throws IOException {
+ Listmapping-file XML elements.
+ */
+ @SuppressWarnings("unchecked")
+ protected void parseMappingFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
+ ListcreateJpaTemplate.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setEntityManagerFactory
+ * @see #setEntityManager
+ * @see #createJpaTemplate
+ * @see #setJpaTemplate
+ * @see org.springframework.orm.jpa.JpaTemplate
+ */
+public abstract class JpaDaoSupport extends DaoSupport {
+
+ private JpaTemplate jpaTemplate;
+
+
+ /**
+ * Set the JPA EntityManagerFactory to be used by this DAO.
+ * Will automatically create a JpaTemplate for the given EntityManagerFactory.
+ * @see #createJpaTemplate
+ * @see #setJpaTemplate
+ */
+ public final void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
+ if (this.jpaTemplate == null || entityManagerFactory != this.jpaTemplate.getEntityManagerFactory()) {
+ this.jpaTemplate = createJpaTemplate(entityManagerFactory);
+ }
+ }
+
+ /**
+ * Create a JpaTemplate for the given EntityManagerFactory.
+ * Only invoked if populating the DAO with a EntityManagerFactory reference!
+ * web.xml;
+ * the default bean name is "entityManagerFactory". Looks up the EntityManagerFactory
+ * on each request, to avoid initialization order issues (when using ContextLoaderServlet,
+ * the root application context will get initialized after this filter).
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see OpenEntityManagerInViewInterceptor
+ * @see org.springframework.orm.jpa.JpaInterceptor
+ * @see org.springframework.orm.jpa.JpaTransactionManager
+ * @see org.springframework.orm.jpa.JpaTemplate#execute
+ * @see org.springframework.orm.jpa.SharedEntityManagerCreator
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
+
+ public static final String DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";
+
+ private String entityManagerFactoryBeanName = DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME;
+
+
+ /**
+ * Set the bean name of the EntityManagerFactory to fetch from Spring's
+ * root application context. Default is "entityManagerFactory".
+ * @see #DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME
+ */
+ public void setEntityManagerFactoryBeanName(String entityManagerFactoryBeanName) {
+ this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;
+ }
+
+ /**
+ * Return the bean name of the EntityManagerFactory to fetch from Spring's
+ * root application context.
+ */
+ protected String getEntityManagerFactoryBeanName() {
+ return this.entityManagerFactoryBeanName;
+ }
+
+
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ EntityManagerFactory emf = lookupEntityManagerFactory(request);
+ boolean participate = false;
+
+ if (TransactionSynchronizationManager.hasResource(emf)) {
+ // Do not modify the EntityManager: just set the participate flag.
+ participate = true;
+ }
+ else {
+ logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
+ try {
+ EntityManager em = createEntityManager(emf);
+ TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));
+ }
+ catch (PersistenceException ex) {
+ throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
+ }
+ }
+
+ try {
+ filterChain.doFilter(request, response);
+ }
+
+ finally {
+ if (!participate) {
+ EntityManagerHolder emHolder = (EntityManagerHolder)
+ TransactionSynchronizationManager.unbindResource(emf);
+ logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
+ EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
+ }
+ }
+ }
+
+ /**
+ * Look up the EntityManagerFactory that this filter should use,
+ * taking the current HTTP request as argument.
+ * lookupEntityManagerFactory
+ * without arguments.
+ * @return the EntityManagerFactory to use
+ * @see #lookupEntityManagerFactory()
+ */
+ protected EntityManagerFactory lookupEntityManagerFactory(HttpServletRequest request) {
+ return lookupEntityManagerFactory();
+ }
+
+ /**
+ * Look up the EntityManagerFactory that this filter should use.
+ * The default implementation looks for a bean with the specified name
+ * in Spring's root application context.
+ * @return the EntityManagerFactory to use
+ * @see #getEntityManagerFactoryBeanName
+ */
+ protected EntityManagerFactory lookupEntityManagerFactory() {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using EntityManagerFactory '" + getEntityManagerFactoryBeanName() +
+ "' for OpenEntityManagerInViewFilter");
+ }
+ WebApplicationContext wac =
+ WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
+ return (EntityManagerFactory)
+ wac.getBean(getEntityManagerFactoryBeanName(), EntityManagerFactory.class);
+ }
+
+ /**
+ * Create a JPA EntityManager to be bound to a request.
+ * EntityManagerFactory
+ * and EntityManager if the annotated fields or methods are declared as such.
+ * The actual type will be verified early, with the exception of a shared ("transactional")
+ * EntityManager reference, where type mismatches might be detected as late
+ * as on the first actual invocation.
+ *
+ * @PersistenceUnit and @PersistenceContext
+ * with the "unitName" attribute, or no attribute at all (for the default unit).
+ * If those annotations are present with the "name" attribute at the class level,
+ * they will simply be ignored, since those only serve as deployment hint
+ * (as per the Java EE 5 specification).
+ *
+ * jee:jndi-lookup
+ * XML configuration element (with the bean name matching the requested unit name).
+ * In both cases, the post-processor definition will look as simple as this:
+ *
+ *
+ * <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
+ *
+ * In the JNDI case, specify the corresponding JNDI names in this post-processor's
+ * {@link #setPersistenceUnits "persistenceUnits" map}, typically with matching
+ * persistence-unit-ref entries in the Java EE deployment descriptor.
+ * By default, those names are considered as resource references (according to the
+ * Java EE resource-ref convention), located underneath the "java:comp/env/" namespace.
+ * For example:
+ *
+ *
+ * <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
+ * <property name="persistenceUnits">
+ * <map/gt;
+ * <entry key="unit1" value="persistence/unit1"/>
+ * <entry key="unit2" value="persistence/unit2"/>
+ * </map/gt;
+ * </property>
+ * </bean>
+ *
+ * In this case, the specified persistence units will always be resolved in JNDI
+ * rather than as Spring-defined beans. The entire persistence unit deployment,
+ * including the weaving of persistent classes, is then up to the Java EE server.
+ * Persistence contexts (i.e. EntityManager references) will be built based on
+ * those server-provided EntityManagerFactory references, using Spring's own
+ * transaction synchronization facilities for transactional EntityManager handling
+ * (typically with Spring's @Transactional annotation for demarcation
+ * and {@link org.springframework.transaction.jta.JtaTransactionManager} as backend).
+ *
+ * persistence-context-ref entries in the
+ * Java EE deployment descriptor. For example:
+ *
+ *
+ * <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
+ * <property name="persistenceContexts">
+ * <map/gt;
+ * <entry key="unit1" value="persistence/context1"/>
+ * <entry key="unit2" value="persistence/context2"/>
+ * </map/gt;
+ * </property>
+ * </bean>
+ *
+ * If the application only obtains EntityManager references in the first place,
+ * this is all you need to specify. If you need EntityManagerFactory references
+ * as well, specify entries for both "persistenceUnits" and "persistenceContexts",
+ * pointing to matching JNDI locations.
+ *
+ * @PersistenceContext with type EXTENDED in
+ * Spring beans defined with scope 'singleton' (Spring's default scope).
+ * Extended EntityManagers are not thread-safe, hence they must not be used
+ * in concurrently accessed beans (which Spring-managed singletons usually are).
+ *
+ *