Initial import of ORM

This commit is contained in:
Arjen Poutsma 2008-10-24 09:06:30 +00:00
parent c236f9fac7
commit d1061e7e9f
160 changed files with 28111 additions and 0 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="org.springframework.orm">
<property file="${basedir}/../build.properties"/>
<import file="${basedir}/../build-spring-framework/package-bundle.xml"/>
<import file="${basedir}/../spring-build/standard/default.xml"/>
</project>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
<ivy-module
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
version="1.3">
<info organisation="org.springframework" module="${ant.project.name}">
<license name="Apache 2.0" url="http://www.apache.org/licenses/LICENSE-2.0"/>
</info>
<configurations>
<include file="${spring.build.dir}/common/default-ivy-configurations.xml"/>
</configurations>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" conf="compile->runtime" />
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.beans" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.aop" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.transaction" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.jdbc" rev="latest.integration" conf="compile->compile" />
<!-- optional dependencies -->
<dependency org="org.springframework" name="org.springframework.context" rev="latest.integration" conf="optional->compile" />
<dependency org="org.springframework" name="org.springframework.web" rev="latest.integration" conf="optional->compile" />
<dependency org="org.hibernate" name="com.springsource.org.hibernate" rev="3.2.6.ga" conf="optional->compile" />
<dependency org="org.hibernate" name="com.springsource.org.hibernate.annotations.common" rev="3.3.0.ga" conf="optional->compile" />
<dependency org="com.oracle.toplink" name="com.springsource.oracle.toplink" rev="10.1.3" conf="optional->compile" />
<dependency org="javax.persistence" name="com.springsource.javax.persistence" rev="1.0.0" conf="optional->compile" />
<dependency org="com.oracle.toplink.essentials" name="com.springsource.oracle.toplink.essentials" rev="2.0.0.b41-beta2" conf="optional->compile" />
<dependency org="org.apache.openjpa" name="com.springsource.org.apache.openjpa.persistence" rev="1.0.2" conf="optional->compile" />
<dependency org="org.hibernate" name="com.springsource.org.hibernate.ejb" rev="3.3.2.GA" conf="optional->compile" />
<dependency org="org.eclipse.persistence" name="com.springsource.org.eclipse.persistence.jpa" rev="1.0.1" conf="optional->compile" />
<dependency org="javax.jdo" name="com.springsource.javax.jdo" rev="2.1.0" conf="optional->compile" />
<dependency org="org.apache.ibatis" name="com.springsource.com.ibatis" rev="2.3.0.677" conf="optional->compile" />
<dependency org="javax.servlet" name="com.springsource.javax.servlet" rev="2.5.0" conf="optional->compile" />
<dependency org="javax.transaction" name="com.springsource.javax.transaction" rev="1.1.0" conf="optional->compile" />
<!-- test dependencies -->
<dependency org="org.junit" name="com.springsource.org.junit" rev="4.4.0" conf="test->runtime" />
</dependencies>
</ivy-module>

View File

@ -0,0 +1,59 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.core</artifactId>
<packaging>jar</packaging>
<name>Spring Core Abstractions and Utilities</name>
<version>3.0.0.M1</version>
<repositories>
<repository>
<id>com.springsource.repository.bundles.external</id>
<name>SpringSource Enterprise Bundle Repository - External Bundle Releases</name>
<url>http://repository.springsource.com/maven/bundles/external</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>com.springsource.org.apache.commons.logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.log4j</groupId>
<artifactId>com.springsource.org.apache.log4j</artifactId>
<version>1.2.15</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>com.springsource.org.apache.commons.collections</artifactId>
<version>3.2.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<version>1.6.2.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.objectweb.asm</groupId>
<artifactId>com.springsource.org.objectweb.asm.commons</artifactId>
<version>2.2.3</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,154 @@
/*
* 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;
import org.springframework.dao.OptimisticLockingFailureException;
/**
* Exception thrown on an optimistic locking violation for a mapped object.
* Provides information about the persistent class and the identifier.
*
* @author Juergen Hoeller
* @since 13.10.2003
*/
public class ObjectOptimisticLockingFailureException extends OptimisticLockingFailureException {
private Object persistentClass;
private Object identifier;
/**
* Create a general ObjectOptimisticLockingFailureException with the given message,
* without any information on the affected object.
* @param msg the detail message
* @param cause the source exception
*/
public ObjectOptimisticLockingFailureException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Create a new ObjectOptimisticLockingFailureException for the given object,
* with the default "optimistic locking failed" message.
* @param persistentClass the persistent class
* @param identifier the ID of the object for which the locking failed
*/
public ObjectOptimisticLockingFailureException(Class persistentClass, Object identifier) {
this(persistentClass, identifier, null);
}
/**
* Create a new ObjectOptimisticLockingFailureException for the given object,
* with the default "optimistic locking failed" message.
* @param persistentClass the persistent class
* @param identifier the ID of the object for which the locking failed
* @param cause the source exception
*/
public ObjectOptimisticLockingFailureException(
Class persistentClass, Object identifier, Throwable cause) {
this(persistentClass, identifier,
"Object of class [" + persistentClass.getName() + "] with identifier [" + identifier +
"]: optimistic locking failed", cause);
}
/**
* Create a new ObjectOptimisticLockingFailureException for the given object,
* with the given explicit message.
* @param persistentClass the persistent class
* @param identifier the ID of the object for which the locking failed
* @param msg the detail message
* @param cause the source exception
*/
public ObjectOptimisticLockingFailureException(
Class persistentClass, Object identifier, String msg, Throwable cause) {
super(msg, cause);
this.persistentClass = persistentClass;
this.identifier = identifier;
}
/**
* Create a new ObjectOptimisticLockingFailureException for the given object,
* with the default "optimistic locking failed" message.
* @param persistentClassName the name of the persistent class
* @param identifier the ID of the object for which the locking failed
*/
public ObjectOptimisticLockingFailureException(String persistentClassName, Object identifier) {
this(persistentClassName, identifier, null);
}
/**
* Create a new ObjectOptimisticLockingFailureException for the given object,
* with the default "optimistic locking failed" message.
* @param persistentClassName the name of the persistent class
* @param identifier the ID of the object for which the locking failed
* @param cause the source exception
*/
public ObjectOptimisticLockingFailureException(
String persistentClassName, Object identifier, Throwable cause) {
this(persistentClassName, identifier,
"Object of class [" + persistentClassName + "] with identifier [" + identifier +
"]: optimistic locking failed", cause);
}
/**
* Create a new ObjectOptimisticLockingFailureException for the given object,
* with the given explicit message.
* @param persistentClassName the name of the persistent class
* @param identifier the ID of the object for which the locking failed
* @param msg the detail message
* @param cause the source exception
*/
public ObjectOptimisticLockingFailureException(
String persistentClassName, Object identifier, String msg, Throwable cause) {
super(msg, cause);
this.persistentClass = persistentClassName;
this.identifier = identifier;
}
/**
* Return the persistent class of the object for which the locking failed.
* If no Class was specified, this method returns null.
*/
public Class getPersistentClass() {
return (this.persistentClass instanceof Class ? (Class) this.persistentClass : null);
}
/**
* Return the name of the persistent class of the object for which the locking failed.
* Will work for both Class objects and String names.
*/
public String getPersistentClassName() {
if (this.persistentClass instanceof Class) {
return ((Class) this.persistentClass).getName();
}
return (this.persistentClass != null ? this.persistentClass.toString() : null);
}
/**
* Return the identifier of the object for which the locking failed.
*/
public Object getIdentifier() {
return identifier;
}
}

View File

@ -0,0 +1,128 @@
/*
* 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;
import org.springframework.dao.DataRetrievalFailureException;
/**
* Exception thrown if a mapped object could not be retrieved via its identifier.
* Provides information about the persistent class and the identifier.
*
* @author Juergen Hoeller
* @since 13.10.2003
*/
public class ObjectRetrievalFailureException extends DataRetrievalFailureException {
private Object persistentClass;
private Object identifier;
/**
* Create a general ObjectRetrievalFailureException with the given message,
* without any information on the affected object.
* @param msg the detail message
* @param cause the source exception
*/
public ObjectRetrievalFailureException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Create a new ObjectRetrievalFailureException for the given object,
* with the default "not found" message.
* @param persistentClass the persistent class
* @param identifier the ID of the object that should have been retrieved
*/
public ObjectRetrievalFailureException(Class persistentClass, Object identifier) {
this(persistentClass, identifier,
"Object of class [" + persistentClass.getName() + "] with identifier [" + identifier + "]: not found",
null);
}
/**
* Create a new ObjectRetrievalFailureException for the given object,
* with the given explicit message and exception.
* @param persistentClass the persistent class
* @param identifier the ID of the object that should have been retrieved
* @param msg the detail message
* @param cause the source exception
*/
public ObjectRetrievalFailureException(
Class persistentClass, Object identifier, String msg, Throwable cause) {
super(msg, cause);
this.persistentClass = persistentClass;
this.identifier = identifier;
}
/**
* Create a new ObjectRetrievalFailureException for the given object,
* with the default "not found" message.
* @param persistentClassName the name of the persistent class
* @param identifier the ID of the object that should have been retrieved
*/
public ObjectRetrievalFailureException(String persistentClassName, Object identifier) {
this(persistentClassName, identifier,
"Object of class [" + persistentClassName + "] with identifier [" + identifier + "]: not found",
null);
}
/**
* Create a new ObjectRetrievalFailureException for the given object,
* with the given explicit message and exception.
* @param persistentClassName the name of the persistent class
* @param identifier the ID of the object that should have been retrieved
* @param msg the detail message
* @param cause the source exception
*/
public ObjectRetrievalFailureException(
String persistentClassName, Object identifier, String msg, Throwable cause) {
super(msg, cause);
this.persistentClass = persistentClassName;
this.identifier = identifier;
}
/**
* Return the persistent class of the object that was not found.
* If no Class was specified, this method returns null.
*/
public Class getPersistentClass() {
return (this.persistentClass instanceof Class ? (Class) this.persistentClass : null);
}
/**
* Return the name of the persistent class of the object that was not found.
* Will work for both Class objects and String names.
*/
public String getPersistentClassName() {
if (this.persistentClass instanceof Class) {
return ((Class) this.persistentClass).getName();
}
return (this.persistentClass != null ? this.persistentClass.toString() : null);
}
/**
* Return the identifier of the object that was not found.
*/
public Object getIdentifier() {
return identifier;
}
}

View File

@ -0,0 +1,336 @@
/*
* 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.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.SessionFactory;
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.jdbc.support.SQLExceptionTranslator;
/**
* Abstract {@link org.springframework.beans.factory.FactoryBean} that creates
* a Hibernate {@link org.hibernate.SessionFactory} within a Spring application
* context, providing general infrastructure not related to Hibernate's
* specific configuration API.
*
* <p>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.
*
* <p>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.
* <p>If this is set, the Hibernate settings should not define
* a connection provider to avoid meaningless double configuration.
* <p>If using HibernateTransactionManager as transaction strategy, consider
* proxying your target DataSource with a LazyConnectionDataSourceProxy.
* This defers fetching of an actual JDBC Connection until the first JDBC
* Statement gets executed, even within JDBC transactions (as performed by
* HibernateTransactionManager). Such lazy fetching is particularly beneficial
* for read-only operations, in particular if the chances of resolving the
* result in the second-level cache are high.
* <p>As JTA and transactional JNDI DataSources already provide lazy enlistment
* of JDBC Connections, LazyConnectionDataSourceProxy does not add value with
* JTA (i.e. Spring's JtaTransactionManager) as transaction strategy.
* @see #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.
* <p>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.
* <p>If you switch this flag to "true", Spring's Hibernate access will be able to
* <i>participate in JDBC-based transactions managed outside of Hibernate</i>
* (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.
* <p>A further benefit of this option is that <i>plain Sessions opened directly
* via the SessionFactory</i>, outside of Spring's Hibernate support, will still
* participate in active Spring-managed transactions. However, consider using
* Hibernate's <code>getCurrentSession()</code> method instead (see javadoc of
* "exposeTransactionAwareSessionFactory" property).
* <p><b>WARNING:</b> 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".
* <p>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 <code>getCurrentSession()</code> method, returning the
* Session that's associated with the current Spring-managed transaction, if any.
* <p>Default is "true", letting data access code work with the plain
* Hibernate SessionFactory and its <code>getCurrentSession()</code> 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, <code>getCurrentSession()</code> will also seamlessly work with
* a request-scoped Session managed by OpenSessionInViewFilter/Interceptor.
* <p>Turn this flag off to expose the plain Hibernate SessionFactory with
* Hibernate's default <code>getCurrentSession()</code> 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.
* <p>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.
* <p>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 <code>null</code>)
* @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.
* <p>Converts the exception if it is a HibernateException;
* else returns <code>null</code> 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
* <code>org.springframework.dao</code> hierarchy.
* <p>Will automatically apply a specified SQLExceptionTranslator to a
* Hibernate JDBCException, else rely on Hibernate's default translation.
* @param ex HibernateException that occured
* @return a corresponding DataAccessException
* @see SessionFactoryUtils#convertHibernateAccessException
* @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
* <code>getSessionFactory()</code> at this point.
* <p>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
* <code>getSessionFactory()</code> at this point.
* <p>This implementation is empty.
* @see #getSessionFactory()
*/
protected void beforeSessionFactoryDestruction() {
}
}

View File

@ -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.
*
* <p>Typically defined as an inner bean within a LocalSessionFactoryBean
* definition, as the list element for the "filterDefinitions" bean property.
* For example:
*
* <pre>
* &lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt;
* ...
* &lt;property name="filterDefinitions"&gt;
* &lt;list&gt;
* &lt;bean class="org.springframework.orm.hibernate3.FilterDefinitionFactoryBean"&gt;
* &lt;property name="filterName" value="myFilter"/&gt;
* &lt;property name="parameterTypes"&gt;
* &lt;props&gt;
* &lt;prop key="myParam"&gt;string&lt;/prop&gt;
* &lt;prop key="myOtherParam"&gt;long&lt;/prop&gt;
* &lt;/props&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* ...
* &lt;/bean&gt;</pre>
*
* 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;
}
}

View File

@ -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.
*
* <p>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.
* <p>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.
* <p>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:
* <ul>
* <li>additional communication roundtrips with the database, instead of a
* single batch at transaction commit;
* <li>the fact that an actual database rollback is needed if the Hibernate
* transaction rolls back (due to already submitted SQL statements).
* </ul>
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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 <b>new</b> Session created by this object.
* <p>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 <code>null</code> 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.
* <p>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.
* <p>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.
* <p>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 <code>null</code> 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 <code>org.springframework.dao</code> hierarchy.
* <p>Will automatically apply a specified SQLExceptionTranslator to a
* Hibernate JDBCException, else rely on Hibernate's default translation.
* @param ex HibernateException that occured
* @return a corresponding DataAccessException
* @see SessionFactoryUtils#convertHibernateAccessException
* @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 <code>org.springframework.dao</code> 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
* <code>org.springframework.dao</code> hierarchy. Can be overridden in subclasses.
* <p>Note that a direct SQLException can just occur when callback code
* performs direct JDBC access via <code>Session.connection()</code>.
* @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.
* <p>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]);
}
}
}
}

View File

@ -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 <code>Session.load/find/update</code> to perform
* some operations on persistent objects. It can also perform direct JDBC operations
* via Hibernate's <code>Session.connection()</code>, operating on a JDBC Connection.
*
* <p>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 <code>HibernateTemplate.execute</code> with an active
* Hibernate <code>Session</code>. Does not need to care about activating
* or closing the <code>Session</code>, or handling transactions.
*
* <p>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.
*
* <p>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 <code>null</code> 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;
}

View File

@ -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.
*
* <p>Application code must retrieve a Hibernate Session via the
* <code>SessionFactoryUtils.getSession</code> method or - preferably -
* Hibernate's own <code>SessionFactory.getCurrentSession()</code> method, to be
* able to detect a thread-bound Session. Typically, the code will look like as follows:
*
* <pre>
* public void doSomeDataAccessAction() {
* Session session = this.sessionFactory.getCurrentSession();
* ...
* // No need to close the Session or translate exceptions!
* }</pre>
*
* Note that this interceptor automatically translates HibernateExceptions,
* via delegating to the <code>SessionFactoryUtils.convertHibernateAccessException</code>
* method that converts them to exceptions that are compatible with the
* <code>org.springframework.dao</code> exception hierarchy (like HibernateTemplate does).
* This can be turned off if the raw exceptions are preferred.
*
* <p>This class can be considered a declarative alternative to HibernateTemplate's
* callback approach. The advantages are:
* <ul>
* <li>no anonymous classes necessary for callback implementations;
* <li>the possibility to throw any application exceptions from within data access code.
* </ul>
*
* <p>The drawback is the dependency on interceptor configuration. However, note
* that this interceptor is usually <i>not</i> 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 <code>org.springframework.dao</code> exception hierarchy.
* <p>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());
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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.
*
* <p>Defines <code>HibernateTemplate</code>'s data access methods that
* mirror various {@link org.hibernate.Session} methods. Users are
* strongly encouraged to read the Hibernate <code>Session</code> javadocs
* for details on the semantics of those methods.
*
* <p>Note that operations that return an {@link java.util.Iterator} (i.e.
* <code>iterate(..)</code>) 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 <code>Iterator</code> won't be able to read
* results from its {@link java.sql.ResultSet} anymore, as the underlying
* Hibernate <code>Session</code> will already have been closed.
*
* <p>Note that lazy loading will just work with an open Hibernate
* <code>Session</code>, 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: <code>contains</code>, <code>evict</code>, <code>lock</code>,
* <code>flush</code>, <code>clear</code>.
*
* @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}.
* <p>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.
* <p>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 <code>Session</code> 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 <code>null</code>
* @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}.
* <p>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 <code>null</code>
* @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 <code>null</code> if not found.
* <p>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 <code>null</code> 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 <code>null</code> if not found.
* <p>Obtains the specified lock mode if the instance exists.
* <p>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 <code>null</code> 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 <code>null</code> if not found.
* <p>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 <code>null</code> 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 <code>null</code> if not found.
* Obtains the specified lock mode if the instance exists.
* <p>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 <code>null</code> 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.
* <p>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.
* <p>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.
* <p>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.
* <p>Obtains the specified lock mode if the instance exists.
* <p>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.
* <p>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 <code>Filter</code> instance can be used to set filter parameters.
* @param filterName the name of the filter
* @return the enabled Hibernate <code>Filter</code> (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}.
* <p>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}.
* <p>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 <code>Session</code>)
* @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 <code>Session</code>.
* @param entityName the name of the persistent entity
* @param entity the persistent instance to save or update
* (to be associated with the Hibernate <code>Session</code>)
* @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 <code>Session</code>.
* @param entities the persistent instances to save or update
* (to be associated with the Hibernate <code>Session</code>)
* @throws DataAccessException in case of Hibernate errors
* @deprecated as of Spring 2.5, in favor of individual
* <code>saveOrUpdate</code> or <code>merge</code> 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.
* <p>Similar to <code>save</code>, 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.
* <p>Similar to <code>save</code>, 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.
* <p>Similar to <code>saveOrUpdate</code>, 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.
* <p>Note that <code>merge</code> will <i>not</i> update the identifiers
* in the passed-in object graph (in contrast to TopLink)! Consider
* registering Spring's <code>IdTransferringMergeEventListener</code> 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.
* <p>Similar to <code>saveOrUpdate</code>, 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.
* <p>Note that <code>merge</code> will <i>not</i> update the identifiers
* in the passed-in object graph (in contrast to TopLink)! Consider
* registering Spring's <code>IdTransferringMergeEventListener</code>
* 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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
* <i>named</i> 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <b>Note: Do not reuse criteria objects! They need to recreated per execution,
* due to the suboptimal design of Hibernate's criteria facility.</b>
* @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.
* <b>Note: Do not reuse criteria objects! They need to recreated per execution,
* due to the suboptimal design of Hibernate's criteria facility.</b>
* @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.
* <p>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.
* <p>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.
* <p>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
* <code>iterate(..)</code> operations, instead of waiting until the
* session is closed or disconnected.
* @param it the <code>Iterator</code> to close
* @throws DataAccessException if the <code>Iterator</code> 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;
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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
* <code>org.springframework.dao</code> 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);
}
}

View File

@ -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
* <code>SessionFactory.getCurrentSession()</code> is required for Hibernate
* access code that needs to support this transaction handling mechanism.
*
* <p>Supports custom isolation levels, and timeouts that get applied as
* Hibernate transaction timeouts.
*
* <p>This transaction manager is appropriate for applications that use a single
* Hibernate SessionFactory for transactional data access, but it also supports
* direct DataSource access within a transaction (i.e. plain JDBC code working
* with the same DataSource). This allows for mixing services which access Hibernate
* and services which use plain JDBC (without being aware of Hibernate)!
* Application code needs to stick to the same simple Connection lookup pattern as
* with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
* (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
* or going through a
* {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
*
* <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
* this instance needs to be aware of the DataSource ({@link #setDataSource}).
* The given DataSource should obviously match the one used by the given
* SessionFactory. 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).
*
* <p>JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager})
* is necessary for accessing multiple transactional resources within the same
* transaction. The DataSource that Hibernate uses needs to be JTA-enabled in
* such a scenario (see container setup). 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.
*
* <p>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.
*
* <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
* Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
* flag defaults to "false", though, as nested transactions will just apply to the
* JDBC Connection, not to the Hibernate Session and its cached objects. You can
* manually set the flag to "true" if you want to use nested transactions for
* JDBC access code which participates in Hibernate transactions (provided that
* your JDBC driver supports Savepoints). <i>Note that Hibernate itself does not
* support nested transactions! Hence, do not expect Hibernate access code to
* semantically participate in a nested transaction.</i>
*
* <p>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.
* <p>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.
* <p>A transactional JDBC Connection for this DataSource will be provided to
* application code accessing this DataSource directly via DataSourceUtils
* or JdbcTemplate. The Connection will be taken from the Hibernate Session.
* <p>The DataSource specified here should be the target DataSource to manage
* transactions for, not a TransactionAwareDataSourceProxy. Only data access
* code may work with TransactionAwareDataSourceProxy, while the transaction
* manager needs to work on the underlying target DataSource. If there's
* nevertheless a TransactionAwareDataSourceProxy passed in, it will be
* unwrapped to extract its target DataSource.
* @see #setAutodetectDataSource
* @see 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 <code>setDataSource</code>. Default is "true".
* <p>Can be turned off to deliberately ignore an available DataSource, in order
* to not expose Hibernate transactions as JDBC transactions for that DataSource.
* @see #setDataSource
* @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.
* <p>Default is "true". If you turn this flag off, the transaction manager
* will not support per-transaction isolation levels anymore. It will not
* call <code>Connection.setReadOnly(true)</code> for read-only transactions
* anymore either. If this flag is turned off, no cleanup of a JDBC Connection
* is required after a transaction, since no Connection settings will get modified.
* <p>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).
* <p>Default is "false", i.e. using a Spring-managed Session: taking the current
* thread-bound Session if available (e.g. in an Open-Session-in-View scenario),
* creating a new Session for the current transaction otherwise.
* <p>Switch this flag to "true" in order to enforce use of a Hibernate-managed Session.
* Note that this requires {@link org.hibernate.SessionFactory#getCurrentSession()}
* to always return a proper Session when called for a Spring-managed transaction;
* transaction begin will fail if the <code>getCurrentSession()</code> call fails.
* <p>This mode will typically be used in combination with a custom Hibernate
* {@link org.hibernate.context.CurrentSessionContext} implementation that stores
* Sessions in a place other than Spring's TransactionSynchronizationManager.
* It may also be used in combination with Spring's Open-Session-in-View support
* (using Spring's default {@link SpringSessionContext}), in which case it subtly
* differs from the Spring-managed Session mode: The pre-bound Session will <i>not</i>
* receive a <code>clear()</code> call (on rollback) or a <code>disconnect()</code>
* call (on transaction completion) in such a scenario; this is rather left up
* to a custom CurrentSessionContext implementation (if desired).
*/
public void setHibernateManagedSession(boolean hibernateManagedSession) {
this.hibernateManagedSession = hibernateManagedSession;
}
/**
* Set whether to perform an early flush before proceeding with a commit.
* <p>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 <code>beforeCommit</code> callbacks of registered
* {@link org.springframework.transaction.support.TransactionSynchronization}
* objects.
* <p>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.
* <p>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.
* <p>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.
* <p>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 <code>null</code> 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.
* <p>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.
* <p>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 <code>true</code>.
* @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 <code>org.springframework.dao</code> hierarchy.
* <p>Will automatically apply a specified SQLExceptionTranslator to a
* Hibernate JDBCException, else rely on Hibernate's default translation.
* @param ex HibernateException that occured
* @return a corresponding DataAccessException
* @see SessionFactoryUtils#convertHibernateAccessException
* @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 <code>org.springframework.dao</code> 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.
* <p>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 <code>doSuspend</code> and <code>doResume</code>.
*/
private static class SuspendedResourcesHolder {
private final SessionHolder sessionHolder;
private final ConnectionHolder connectionHolder;
private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
this.sessionHolder = sessionHolder;
this.connectionHolder = conHolder;
}
private SessionHolder getSessionHolder() {
return this.sessionHolder;
}
private ConnectionHolder getConnectionHolder() {
return this.connectionHolder;
}
}
}

View File

@ -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();
}
}

View File

@ -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.
* <p>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 <code>Connection.close</code>.
* @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 <code>false</code>: We cannot guarantee
* to receive the same Connection within a transaction, not even when
* dealing with a JNDI DataSource.
*/
public boolean supportsAggressiveRelease() {
return false;
}
}

View File

@ -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 <code>true</code>,
* since we're assuming a JTA DataSource.
*/
public boolean supportsAggressiveRelease() {
return true;
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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;
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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 <code>DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100</code>
* to execute Session cleanup before JDBC Connection cleanup, if any.
* @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
*/
public static final int SESSION_SYNCHRONIZATION_ORDER =
DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
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 <code>null</code> 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 <code>null</code>)
* @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 <code>true</code>.
* <p>This is the <code>getSession</code> method used by typical data access code,
* in combination with <code>releaseSession</code> 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 <code>false</code>
* @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.
* <p>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 <code>null</code> if none
* @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
* Session on transaction synchronization (may be <code>null</code>; 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 <code>true</code>.
* <p>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 <code>true</code>.
* <p>Same as {@link #getSession}, but throwing the original HibernateException.
* @param sessionFactory Hibernate SessionFactory to create the session with
* @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none
* @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
* Session on transaction synchronization (may be <code>null</code>)
* @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 <code>false</code>
*/
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 <code>null</code>)
* @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 <code>null</code>)
*/
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.
* <p>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.
* <p>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 <code>null</code> 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 <code>Object.toString()</code>:
* the fully qualified class name + "@" + the identity hash code.
* <p>The sole reason why this is necessary is because Hibernate3's
* <code>Session.toString()</code> 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 <code>null</code>)
* @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 <code>null</code>)
* @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 <code>null</code>)
* @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 <code>org.springframework.dao</code> 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).
* <p>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 <code>null</code>)
* @param sessionFactory Hibernate SessionFactory that the Session was created with
* (may be <code>null</code>)
*/
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 <code>null</code>)
* @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 <code>null</code>)
* @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);
}
}
}
}

View File

@ -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.
*
* <p>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;
}
}

View File

@ -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.
*
* <p>Used by Spring's {@link LocalSessionFactoryBean} when told to expose a
* transaction-aware SessionFactory. This is the default as of Spring 2.5.
*
* <p>This CurrentSessionContext implementation can also be specified in custom
* SessionFactory setup through the "hibernate.current_session_context_class"
* property, with the fully qualified name of this class as value.
*
* @author Juergen Hoeller
* @since 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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -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.
* <p>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();
}
}

View File

@ -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 <code>true</code>: We can guarantee
* to receive the same Connection within a transaction, as we are
* exposing a TransactionAwareDataSourceProxy.
*/
public boolean supportsAggressiveRelease() {
return true;
}
}

View File

@ -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.
*
* <p>Typically defined as inner bean within a LocalSessionFactoryBean
* definition, as list element for the "typeDefinitions" bean property.
* For example:
*
* <pre>
* &lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt;
* ...
* &lt;property name="typeDefinitions"&gt;
* &lt;list&gt;
* &lt;bean class="org.springframework.orm.hibernate3.TypeDefinitionBean"&gt;
* &lt;property name="typeName" value="myType"/&gt;
* &lt;property name="typeClass" value="mypackage.MyTypeClass"/&gt;
* &lt;/bean&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* ...
* &lt;/bean&gt;</pre>
*
* 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");
}
}
}

View File

@ -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.
*
* <p>Note: This class requires Hibernate 3.2 or higher, with the
* Java Persistence API and the Hibernate Annotations add-on present.
*
* <p>Example for an AnnotationSessionFactoryBean bean definition:
*
* <pre class="code">
* &lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&gt;
* &lt;property name="dataSource" ref="dataSource"/&gt;
* &lt;property name="annotatedClasses"&gt;
* &lt;list&gt;
* &lt;value&gt;test.package.Foo&lt;/value&gt;
* &lt;value&gt;test.package.Bar&lt;/value&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* Or when using classpath scanning for autodetection of entity classes:
*
* <pre class="code">
* &lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&gt;
* &lt;property name="dataSource" ref="dataSource"/&gt;
* &lt;property name="packagesToScan" value="test.package"/&gt;
* &lt;/bean&gt;</pre>
*
* @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.
* <p>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.
* <p>Default is to search all specified packages for classes annotated with
* <code>@javax.persistence.Entity</code>, <code>@javax.persistence.Embeddable</code>
* or <code>@javax.persistence.MappedSuperclass</code>.
* @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 {
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Support package for the Hibernate3 Annotation add-on,
which supports EJB3-compliant JDK 1.5+ annotations for mappings.
</body>
</html>

View File

@ -0,0 +1,16 @@
<html>
<body>
Package providing integration of
<a href="http://www.hibernate.org">Hibernate3</a>
with Spring concepts.
<p>Contains SessionFactory helper classes, a template plus callback
for Hibernate access, and an implementation of Spring's transaction SPI
for local Hibernate transactions.
<p><b>This package supports Hibernate 3.x only.</b>
See the org.springframework.orm.hibernate package for Hibernate 2.1 support.
</body>
</html>

View File

@ -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.
*
* <p>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.
*
* <p>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 <code>null</code>).
*/
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;
}

View File

@ -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.
*
* <p>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 <code>null</code>).
*/
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);
}
}

View File

@ -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.
*
* <p>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.
* <p>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 <code>null</code>).
*/
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);
}
}
}

View File

@ -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.
*
* <p>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
* <code>getCharacterEncoding()</code> method.
*
* <p>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 <code>null</code>).
*/
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.
* <p>Default is <code>null</code>, 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 <code>null</code>
* to use the platform default encoding
* @see java.lang.String#String(byte[], String)
* @see java.lang.String#getBytes(String)
*/
protected String getCharacterEncoding() {
return null;
}
}

View File

@ -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.
*
* <p>Particularly useful for storing Strings with more than 4000 characters in an
* Oracle database (only possible via CLOBs), in combination with OracleLobHandler.
*
* <p>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 <code>null</code>).
*/
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);
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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!
* <p>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.
* <p><b>Note: The returned HibernateTemplate is a shared instance.</b>
* 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
* <code>new HibernateTemplate(getSessionFactory())</code>, 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".
* <p><b>Note that this is not meant to be invoked from HibernateTemplate code
* but rather just in plain Hibernate code.</b> Either rely on a thread-bound
* Session or use it in combination with {@link #releaseSession}.
* <p>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.
* <p><b>Note that this is not meant to be invoked from HibernateTemplate code
* but rather just in plain Hibernate code.</b> Either rely on a thread-bound
* Session or use it in combination with {@link #releaseSession}.
* <p>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
* <code>org.springframework.dao</code> hierarchy. Will automatically detect
* wrapped SQLExceptions and convert them accordingly.
* <p>Delegates to the
* {@link org.springframework.orm.hibernate3.HibernateTemplate#convertHibernateAccessException}
* method of this DAO's HibernateTemplate.
* <p>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).
* <p>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());
}
}

View File

@ -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 <code>merge</code> method).
*
* <p>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.
*
* <p>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 <code>HibernateClinic</code>
* and <code>TopLinkClinic</code> DAO implementations both use straight merge
* calls, with the Hibernate SessionFactory configuration specifying an
* <code>IdTransferringMergeEventListener</code>.
*
* <p>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());
}
}

View File

@ -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.
*
* <p>This filter makes Hibernate Sessions available via the current thread, which
* will be autodetected by transaction managers. It is suitable for service layer
* transactions via {@link org.springframework.orm.hibernate3.HibernateTransactionManager}
* or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
* as for non-transactional execution (if configured appropriately).
*
* <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session,
* with the flush mode set to <code>FlushMode.NEVER</code>. It assumes to be used
* in combination with service layer transactions that care for the flushing: The
* active transaction manager will temporarily change the flush mode to
* <code>FlushMode.AUTO</code> during a read-write transaction, with the flush
* mode reset to <code>FlushMode.NEVER</code> at the end of each transaction.
* If you intend to use this filter without transactions, consider changing
* the default flush mode (through the "flushMode" property).
*
* <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that
* have not appeared before, through the use of a single Hibernate Session for the
* processing of an entire request. In particular, the reassociation of persistent
* objects with a Hibernate Session has to occur at the very beginning of request
* processing, to avoid clashes with already loaded instances of the same objects.
*
* <p>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.
*
* <p>A single session per request allows for most efficient first-level caching,
* but can cause side effects, for example on <code>saveOrUpdate</code> 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).
*
* <p>Looks up the SessionFactory in Spring's root web application context.
* Supports a "sessionFactoryBeanName" filter init-param in <code>web.xml</code>;
* the default bean name is "sessionFactory". Looks up the SessionFactory on each
* request, to avoid initialization order issues (when using ContextLoaderServlet,
* the root application context will get initialized <i>after</i> this filter).
*
* @author Juergen Hoeller
* @since 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".
* <p>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.
* <p>Can be populated with the corresponding constant name in XML bean
* definitions: e.g. "AUTO".
* <p>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.
* <p>The default implementation delegates to the {@link #lookupSessionFactory()}
* variant without arguments.
* @param request the current request
* @return the SessionFactory to use
*/
protected SessionFactory lookupSessionFactory(HttpServletRequest request) {
return lookupSessionFactory();
}
/**
* Look up the SessionFactory that this filter should use.
* <p>The default implementation looks for a bean with the specified name
* in Spring's root application context.
* @return the SessionFactory to use
* @see #getSessionFactoryBeanName
*/
protected SessionFactory lookupSessionFactory() {
if (logger.isDebugEnabled()) {
logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter");
}
WebApplicationContext wac =
WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
return (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!
* <p>The default implementation delegates to the
* <code>SessionFactoryUtils.getSession</code> method and
* sets the <code>Session</code>'s flush mode to "NEVER".
* <p>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!
* <p>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);
}
}

View File

@ -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 <code>Session</code> to the
* thread for the entire processing of the request.
*
* <p>This class is a concrete expression of the "Open Session in View" pattern, which
* is a pattern that allows for the lazy loading of associations in web views despite
* the original transactions already being completed.
*
* <p>This interceptor makes Hibernate <code>Sessions</code> 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).
*
* <p><b>NOTE</b>: This interceptor will by default <i>not</i> flush the Hibernate
* <code>Session</code>, with the flush mode being set to <code>FlushMode.NEVER</code>.
* It assumes that it will be used in combination with service layer transactions
* that handle the flushing: the active transaction manager will temporarily change
* the flush mode to <code>FlushMode.AUTO</code> during a read-write transaction,
* with the flush mode reset to <code>FlushMode.NEVER</code> at the end of each
* transaction. If you intend to use this interceptor without transactions, consider
* changing the default flush mode (through the
* {@link #setFlushMode(int) "flushMode"} property).
*
* <p>In contrast to {@link OpenSessionInViewFilter}, this interceptor is
* configured in a Spring application context and can thus take advantage of bean
* wiring. It inherits common Hibernate configuration properties from
* {@link org.springframework.orm.hibernate3.HibernateAccessor},
* to be configured in a bean definition.
*
* <p><b>WARNING:</b> Applying this interceptor to existing logic can cause issues
* that have not appeared before, through the use of a single Hibernate
* <code>Session</code> for the processing of an entire request. In particular, the
* reassociation of persistent objects with a Hibernate <code>Session</code> has to
* occur at the very beginning of request processing, to avoid clashes with already
* loaded instances of the same objects.
*
* <p>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.
*
* <p>A single session per request allows for the most efficient first-level caching,
* but can cause side effects, for example on <code>saveOrUpdate</code> 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 <code>SessionFactory</code>
* <code>toString()</code> representation for the "participate in existing
* session handling" request attribute.
* @see #getParticipateAttributeName
*/
public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
private boolean singleSession = true;
/**
* Create a new <code>OpenSessionInViewInterceptor</code>,
* turning the default flushMode to <code>FLUSH_NEVER</code>.
* @see #setFlushMode
*/
public OpenSessionInViewInterceptor() {
setFlushMode(FLUSH_NEVER);
}
/**
* Set whether to use a single session for each request. Default is "true".
* <p>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 <code>Session</code> according to the settings of this
* <code>HibernateAccessor</code> 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 <code>Session</code> before view rendering, if necessary.
* <p>Note that this just applies in {@link #isSingleSession() single session mode}!
* <p>The default is <code>FLUSH_NEVER</code> 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 <code>Session</code> 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.
* <p>The default implementation takes the <code>toString()</code> representation
* of the <code>SessionFactory</code> instance and appends {@link #PARTICIPATE_SUFFIX}.
*/
protected String getParticipateAttributeName() {
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
}
}

View File

@ -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.
*
* <p>Usage example:
*
* <pre>
* &lt;bean id=&quot;sessionFactory&quot; class=&quot;org.springframework.orm.hibernate3.LocalSessionFactoryBean&quot;&gt;
* ...
* &lt;property name=&quot;entityInterceptor&quot;&gt;
* &lt;bean class=&quot;org.springframework.orm.hibernate3.support.ScopedBeanInterceptor&quot;/&gt;
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* @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);
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Classes supporting the <code>org.springframework.orm.hibernate3</code> package.
Contains a DAO base class for HibernateTemplate usage.
</body>
</html>

View File

@ -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 <code>execute</code> 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 <code>SqlMapClientTemplate.execute</code> with an active
* <code>SqlMapExecutor</code>. Does not need to care about activating
* or closing the <code>SqlMapExecutor</code>, or handling transactions.
*
* <p>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.
*
* <p>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 <code>null</code> 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;
}

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p><b>Note:</b> 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.
* <p>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.
* <p>This is an alternative to specifying "&lt;sqlMap&gt;" 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".
* <p>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 <code>&lt;properties&gt;</code> 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.
* <p>If specified, this will override corresponding settings in the SqlMapClient
* properties. Usually, you will specify DataSource and transaction configuration
* <i>either</i> here <i>or</i> in SqlMapClient properties.
* <p>Specifying a DataSource for the SqlMapClient rather than for each individual
* DAO allows for lazy loading, for example when using PaginatedList results.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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
* <code>com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig</code>.
* <p>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.
* <p>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.
* <p>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).
* <p><b>It is strongly recommended to use iBATIS SQL Maps with Spring
* transaction management (or EJB CMT).</b> In this case, the default
* ExternalTransactionConfig is fine. Lazy loading and SQL Maps operations
* without explicit transaction demarcation will execute non-transactionally.
* <p>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:
* <p><ul>
* <li><b>ExternalTransactionConfig</b> supports "DefaultAutoCommit"
* (default: false) and "SetAutoCommitAllowed" (default: true).
* Note that Spring uses SetAutoCommitAllowed = false as default,
* in contrast to the iBATIS default, to always keep the original
* autoCommit value as provided by the connection pool.
* <li><b>JdbcTransactionConfig</b> does not supported any properties.
* <li><b>JtaTransactionConfig</b> supports "UserTransaction"
* (no default), specifying the JNDI location of the JTA UserTransaction
* (usually "java:comp/UserTransaction").
* </ul>
* @see com.ibatis.sqlmap.engine.transaction.TransactionConfig#initialize
* @see com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig
* @see com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig
* @see com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
*/
public void setTransactionConfigProperties(Properties transactionConfigProperties) {
this.transactionConfigProperties = transactionConfigProperties;
}
/**
* Set the LobHandler to be used by the SqlMapClient.
* Will be exposed at config time for TypeHandler implementations.
* @see #getConfigTimeLobHandler
* @see com.ibatis.sqlmap.engine.type.TypeHandler
* @see org.springframework.orm.ibatis.support.ClobStringTypeHandler
* @see org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler
* @see org.springframework.orm.ibatis.support.BlobSerializableTypeHandler
*/
public void setLobHandler(LobHandler lobHandler) {
this.lobHandler = lobHandler;
}
public void afterPropertiesSet() throws Exception {
if (this.lobHandler != null) {
// Make given LobHandler available for SqlMapClient configuration.
// Do early because because mapping resource might refer to custom types.
configTimeLobHandlerHolder.set(this.lobHandler);
}
try {
this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);
// Tell the SqlMapClient to use the given DataSource, if any.
if (this.dataSource != null) {
TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
DataSource dataSourceToUse = this.dataSource;
if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
}
transactionConfig.setDataSource(dataSourceToUse);
transactionConfig.initialize(this.transactionConfigProperties);
applyTransactionConfig(this.sqlMapClient, transactionConfig);
}
}
finally {
if (this.lobHandler != null) {
// Reset LobHandler holder.
configTimeLobHandlerHolder.set(null);
}
}
}
/**
* Build a SqlMapClient instance based on the given standard configuration.
* <p>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 <code>null</code>)
* @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.
* <p>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);
}
}
}

View File

@ -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.
*
* <p>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;
}

View File

@ -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
* <code>org.springframework.dao</code> exception hierarchy.
* Uses the same {@link org.springframework.jdbc.support.SQLExceptionTranslator}
* mechanism as {@link org.springframework.jdbc.core.JdbcTemplate}.
*
* <p>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.
*
* <p>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:
*
* <pre class="code">
* 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;
* }
* });</pre>
*
* 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 <code>null</code>
* @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);
}
}
}

View File

@ -0,0 +1,12 @@
<html>
<body>
Package providing integration of
<a href="http://ibatis.apache.org">iBATIS Database Layer</a>
with Spring concepts.
<p>Contains resource helper classes and template classes for
data access with the iBATIS SqlMapClient API.
</body>
</html>

View File

@ -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.
*
* <p>For writing LOBs, an active Spring transaction synchronization is required,
* to be able to register a synchronization that closes the LobCreator.
*
* <p>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();
}
}
}

View File

@ -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.
*
* <p>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();
}
}

View File

@ -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.
*
* <p>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;
}
}

View File

@ -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.
*
* <p>Particularly useful for storing Strings with more than 4000 characters in an
* Oracle database (only possible via CLOBs), in combination with OracleLobHandler.
*
* <p>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;
}
}

View File

@ -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.
*
* <p>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();
}
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Classes supporting the <code>org.springframework.orm.ibatis</code> package.
Contains a DAO base class for SqlMapClientTemplate usage.
</body>
</html>

View File

@ -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}.
*
* <p>Simply begins a standard JDO transaction in <code>beginTransaction</code>.
* Returns a handle for a JDO2 DataStoreConnection on <code>getJdbcConnection</code>.
* Calls the corresponding JDO2 PersistenceManager operation on <code>flush</code>
* Ignores a given query timeout in <code>applyQueryTimeout</code>.
* Uses a Spring SQLExceptionTranslator for exception translation, if applicable.
*
* <p>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 <code>getJdbcConnection</code>, rather than JDO2's wrapper handle.
*
* <p>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.
* <p>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 <code>Transaction.begin</code>
* 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.
* <p>For pre-JDO2 implementations, override this method to return the
* Connection through the corresponding vendor-specific mechanism, or <code>null</code>
* if the Connection is not retrievable.
* <p><b>NOTE:</b> 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.
* <p>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
* <code>DataSourceUtils.releaseConnection</code>).
* @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.
* <p>If the JDO provider returns a Connection handle that it
* expects the application to close, the dialect needs to invoke
* <code>Connection.close</code> here.
* @see java.sql.Connection#close()
*/
public void releaseJdbcConnection(ConnectionHandle conHandle, PersistenceManager pm)
throws JDOException, SQLException {
}
/**
* This implementation delegates to JDO 2.0's <code>flush</code> method.
* <p>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.
* <p>Converts the exception if it is a JDOException, using this JdoDialect.
* Else returns <code>null</code> 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.
* <p>Default implementation always returns <code>null</code>. 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 <code>null</code> if none found
*/
protected String extractSqlStringFromException(JDOException ex) {
return null;
}
/**
* ConnectionHandle implementation that fetches a new JDO2 DataStoreConnection
* for every <code>getConnection</code> call and closes the Connection on
* <code>releaseConnection</code>. 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);
}
}
}

View File

@ -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.
*
* <p>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).
*
* <p>Eager flushing is just available for specific JDO providers.
* You need to a corresponding JdoDialect to make eager flushing work.
*
* <p>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.
* <p>The dialect object can be used to retrieve the underlying JDBC
* connection and to eagerly flush changes to the database.
* <p>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.
* <p>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.
* <p>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:
* <ul>
* <li>additional communication roundtrips with the database, instead of a
* single batch at transaction commit;
* <li>the fact that an actual database rollback is needed if the JDO
* transaction rolls back (due to already submitted SQL statements).
* </ul>
*/
public void setFlushEager(boolean flushEager) {
this.flushEager = flushEager;
}
/**
* Return if this accessor should flush changes to the database eagerly.
*/
public boolean isFlushEager() {
return flushEager;
}
/**
* Eagerly initialize the JDO dialect, creating a default one
* for the specified PersistenceManagerFactory if none set.
*/
public void afterPropertiesSet() {
if (getPersistenceManagerFactory() == null) {
throw new IllegalArgumentException("persistenceManagerFactory is required");
}
// Build default JdoDialect if none explicitly specified.
if (this.jdoDialect == null) {
this.jdoDialect = new DefaultJdoDialect(getPersistenceManagerFactory().getConnectionFactory());
}
}
/**
* Flush the given JDO persistence manager if necessary.
* @param pm the current JDO PersistenceManager
* @param existingTransaction if executing within an existing transaction
* (within an existing JDO PersistenceManager that won't be closed immediately)
* @throws JDOException in case of JDO flushing errors
*/
protected void flushIfNecessary(PersistenceManager pm, boolean existingTransaction) throws JDOException {
if (isFlushEager()) {
logger.debug("Eagerly flushing JDO persistence manager");
getJdoDialect().flush(pm);
}
}
/**
* Convert the given JDOException to an appropriate exception from the
* <code>org.springframework.dao</code> hierarchy.
* <p>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);
}
}

View File

@ -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.
*
* <p>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 <code>JdoTemplate.execute</code> with an active JDO
* <code>PersistenceManager</code>. Does not need to care about activating
* or closing the <code>PersistenceManager</code>, or handling transactions.
*
* <p>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.
*
* <p>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 <code>null</code> if none
* @throws JDOException if thrown by the JDO API
* @see JdoTemplate#execute
* @see JdoTemplate#executeFind
*/
Object doInJdo(PersistenceManager pm) throws JDOException;
}

View File

@ -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.
*
* <p>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).
*
* <p>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.
* <p>An implementation can configure the JDO Transaction object and then
* invoke <code>begin</code>, or invoke a special begin method that takes,
* for example, an isolation level.
* <p>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 <code>cleanupTransaction</code>.
* <p>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.
* <p>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 <code>releaseJdbcConnection</code> method when not needed anymore.
* <p>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.
* <p>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 <code>releaseJdbcConnection</code>,
* 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
* <code>releaseJdbcConnection</code>, or <code>null</code>
* 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 <code>getJdbcConnection</code>. This should be invoked in any case,
* to allow for proper release of the retrieved Connection handle.
* <p>An implementation might simply do nothing, if the Connection returned
* by <code>getJdbcConnection</code> 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.
* <p>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.
* <p>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.
* <p>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 <code>null</code>)
* @see JdoAccessor#convertJdoAccessException
* @see JdoTransactionManager#convertJdoAccessException
* @see PersistenceManagerFactoryUtils#convertJdoAccessException
* @see org.springframework.dao.DataIntegrityViolationException
* @see org.springframework.jdbc.support.SQLExceptionTranslator
*/
DataAccessException translateException(JDOException ex);
}

View File

@ -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.
*
* <p>Application code must retrieve a JDO PersistenceManager via the
* <code>PersistenceManagerFactoryUtils.getPersistenceManager</code> method,
* to be able to detect a thread-bound PersistenceManager. It is preferable to use
* <code>getPersistenceManager</code> with allowCreate=false, if the code relies on
* the interceptor to provide proper PersistenceManager handling. Typically, the code
* will look like as follows:
*
* <pre>
* public void doSomeDataAccessAction() {
* PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(this.pmf, false);
* ...
* }</pre>
*
* <p>Note that this interceptor automatically translates JDOExceptions, via
* delegating to the <code>PersistenceManagerFactoryUtils.convertJdoAccessException</code>
* method that converts them to exceptions that are compatible with the
* <code>org.springframework.dao</code> exception hierarchy (like JdoTemplate does).
* This can be turned off if the raw exceptions are preferred.
*
* <p>This class can be considered a declarative alternative to JdoTemplate's
* callback approach. The advantages are:
* <ul>
* <li>no anonymous classes necessary for callback implementations;
* <li>the possibility to throw any application exceptions from within data access code.
* </ul>
*
* <p>The drawback is the dependency on interceptor configuration. However, note
* that this interceptor is usually <i>not</i> 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 <code>org.springframework.dao</code> exception hierarchy.
* <p>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());
}
}
}
}

View File

@ -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);
}
}

View File

@ -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.
*
* <p>Defines <code>JdoTemplate</code>'s data access methods that mirror
* various JDO {@link javax.jdo.PersistenceManager} methods. Users are
* strongly encouraged to read the JDO <code>PersistenceManager</code>
* javadocs for details on the semantics of those methods.
*
* <p>Note that lazy loading will just work with an open JDO
* <code>PersistenceManager</code>, 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: <code>evict</code>, <code>evictAll</code>, <code>flush</code>.
*
* <p>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.
* <p>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 <code>null</code>
* @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 <code>null</code>
* @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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>Note that as of JDO 2.0 final, this operation is equivalent to a
* <code>makePersistent</code> 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.
* <p>Note that as of JDO 2.0 final, this operation is equivalent to a
* <code>makePersistentAll</code> 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.
* <p>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 <code>null</code> 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 <code>null</code> if none)
* @param ordering the ordering of the result (or <code>null</code> 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 <code>null</code> 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 <code>null</code> 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 (<code>javax.jdo.Query#JDOQL</code>
* or <code>javax.jdo.Query#SQL</code>, 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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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
* <code>org.springframework.dao</code> exceptions.
*
* @author Juergen Hoeller
* @since 03.06.2003
* @see PersistenceManagerFactoryUtils#convertJdoAccessException
*/
public class JdoSystemException extends UncategorizedDataAccessException {
public JdoSystemException(JDOException ex) {
super(ex.getMessage(), ex);
}
}

View File

@ -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
* <code>org.springframework.dao</code> exception hierarchy.
*
* <p>The central method is <code>execute</code>, 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.
*
* <p>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 <code>org.springframework.dao</code> exceptions.
*
* <p>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.
*
* <p>This class can be considered as direct alternative to working with the
* raw JDO PersistenceManager API (through
* <code>PersistenceManagerFactoryUtils.getPersistenceManager()</code>).
* 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.
*
* <p>{@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.
*
* <p>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: <code>evict</code>, <code>evictAll</code>, <code>flush</code>.
*
* <p><b>NOTE: This class requires JDO 2.0 or higher, as of Spring 2.5.</b>
*
* @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.
* <p>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 <code>close</code> calls and automatically applying transaction
* timeouts (if any).
* <p>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 <code>null</code>
* @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 <code>execute</code> method.
* <p>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 <code>execute</code> method.
* <p>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.
* <p>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();
}
}
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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}).
*
* <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
* this instance needs to be aware of the DataSource ({@link #setDataSource}).
* The given DataSource should obviously match the one used by the given
* 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.
*
* <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
* Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
* flag defaults to "false", though, as nested transactions will just apply to the
* JDBC Connection, not to the 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). <i>Note that JDO itself does not support
* nested transactions! Hence, do not expect JDO access code to semantically
* participate in a nested transaction.</i>
*
* @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.
* <p>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.
* <p>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.
* <p>A transactional JDBC Connection for this DataSource will be provided to
* application code accessing this DataSource directly via DataSourceUtils
* or JdbcTemplate. The Connection will be taken from the JDO PersistenceManager.
* <p>Note that you need to use a JDO dialect for a specific JDO provider to
* allow for exposing JDO transactions as JDBC transactions.
* <p>The DataSource specified here should be the target DataSource to manage
* transactions for, not a TransactionAwareDataSourceProxy. Only data access
* code may work with TransactionAwareDataSourceProxy, while the transaction
* manager needs to work on the underlying target DataSource. If there's
* nevertheless a TransactionAwareDataSourceProxy passed in, it will be
* unwrapped to extract its target DataSource.
* @see #setAutodetectDataSource
* @see #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 <code>getConnectionFactory()</code> method. Default is "true".
* <p>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.
* <p>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.
* <p>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
* <code>org.springframework.dao</code> hierarchy.
* <p>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 <code>doSuspend</code> and <code>doResume</code>.
*/
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;
}
}
}

View File

@ -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);
}
}

View File

@ -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!
*
* <p>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}).
*
* <p><b>NOTE: This class requires JDO 2.0 or higher, as of Spring 2.5.</b>
*
* <p>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.
*
* <p><b>Alternative: Configuration of a PersistenceManagerFactory provider bean</b>
*
* <p>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.
*
* <p>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).
*
* <p>For example, in case of <a href="http://www.jpox.org">JPOX</a>:
*
* <p><pre>
* &lt;bean id="persistenceManagerFactory" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close"&gt;
* &lt;property name="connectionFactory" ref="dataSource"/&gt;
* &lt;property name="nontransactionalRead" value="true"/&gt;
* &lt;/bean&gt;
* </pre>
*
* <p>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.
*
* <p>The <code>close()</code> 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 <code>close()</code> 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.
* <p>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).
* <p>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".
* <p>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".
* <p>Can be used to override values in a JDO properties config file,
* or to specify all necessary properties locally.
* <p>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
* <code>JDOHelper.getPersistenceManagerFactory</code> (if any).
* <p>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.
* <p>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.
* <p>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.
* <p>The default implementation invokes JDOHelper's
* <code>getPersistenceManagerFactory(String)</code> 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.
* <p>The default implementation invokes JDOHelper's
* <code>getPersistenceManagerFactory(Map)</code> 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.
* <p>Converts the exception if it is a JDOException, preferably using a specified
* JdoDialect. Else returns <code>null</code> 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();
}
}

View File

@ -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.
*
* <p>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.
* <p>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 <code>null</code>)
* @return the SQLExceptionTranslator (never <code>null</code>)
* @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 <code>true</code>.
* @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 <code>false</code>
* @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 <code>true</code>.
* <p>Same as <code>getPersistenceManager</code>, 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 <code>false</code>
* @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 <code>null</code>)
* @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 <code>null</code>)
* @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
* <code>org.springframework.dao</code> hierarchy.
* <p>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 <code>null</code>)
*/
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 <code>releasePersistenceManager</code>, but throwing the original JDOException.
* @param pm PersistenceManager to close
* @param pmf PersistenceManagerFactory that the PersistenceManager was created with
* (can be <code>null</code>)
* @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);
}
}
}

View File

@ -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.
*
* <p>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;
}
}

View File

@ -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 <code>getPersistenceManager()</code>, if any.
*
* <p>Essentially, <code>getPersistenceManager()</code> calls get seamlessly
* forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}.
* Furthermore, <code>PersistenceManager.close</code> calls get forwarded to
* {@link PersistenceManagerFactoryUtils#releasePersistenceManager}.
*
* <p>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.
*
* <p>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!
*
* <p>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.
*
* <p>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.
* <p>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 <code>PersistenceManagerFactory.getPersistenceManager()</code>
* call without corresponding <code>PersistenceManager.close()</code> 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();
}
}
}
}

View File

@ -0,0 +1,9 @@
<html>
<body>
Package providing integration of JDO (Java Date Objects) with Spring concepts.
Contains PersistenceManagerFactory helper classes, a template plus callback for JDO
access, and an implementation of Spring's transaction SPI for local JDO transactions.
</body>
</html>

View File

@ -0,0 +1,170 @@
/*
* 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.support;
import javax.jdo.JDOException;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.support.DaoSupport;
import org.springframework.orm.jdo.JdoTemplate;
import org.springframework.orm.jdo.PersistenceManagerFactoryUtils;
/**
* Convenient super class for JDO data access objects.
*
* <p>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.
*
* <p>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
* <code>getPersistenceManager</code> and <code>releasePersistenceManager</code>
* methods are provided for that usage style.
*
* <p>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 <code>createJdoTemplate</code>.
*
* @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!
* <p>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.
* <p>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());
}
}

View File

@ -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.
*
* <p>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.
*
* <p>Looks up the PersistenceManagerFactory in Spring's root web application context.
* Supports a "persistenceManagerFactoryBeanName" filter init-param in <code>web.xml</code>;
* 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 <i>after</i> 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.
* <p>Default implementation delegates to the <code>lookupPersistenceManagerFactory</code>
* 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);
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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;
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Classes supporting the <code>org.springframework.orm.jdo</code> package.
Contains a DAO base class for JdoTemplate usage.
</body>
</html>

View File

@ -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.
*
* <p>Encapsulates the common functionality between the different JPA
* bootstrap contracts (standalone as well as container).
*
* <p>Implements support for standard JPA configuration as well as
* Spring's {@link JpaVendorAdapter} abstraction, and controls the
* EntityManagerFactory's lifecycle.
*
* <p>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.
* <p>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
* <code>Persistence.createEntityManagerFactory</code> (if any).
* <p>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
* <code>Persistence.createEntityManagerFactory</code> (if any).
* <p>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.
* <p>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.
* <p>The default will be taken from the specific JpaVendorAdapter, if any,
* or set to the standard <code>javax.persistence.EntityManagerFactory</code>
* 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.
* <p>The default will be taken from the specific JpaVendorAdapter, if any,
* or set to the standard <code>javax.persistence.EntityManager</code>
* 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 <code>null</code> 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) {
Set<Class> ifcs = new LinkedHashSet<Class>();
if (this.entityManagerFactoryInterface != null) {
ifcs.add(this.entityManagerFactoryInterface);
}
else {
ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader));
}
ifcs.add(EntityManagerFactoryInfo.class);
EntityManagerFactoryPlusOperations plusOperations = null;
if (getJpaDialect() != null && getJpaDialect().supportsEntityManagerFactoryPlusOperations()) {
plusOperations = getJpaDialect().getEntityManagerFactoryPlusOperations(emf);
ifcs.add(EntityManagerFactoryPlusOperations.class);
}
return (EntityManagerFactory) Proxy.newProxyInstance(
this.beanClassLoader, ifcs.toArray(new Class[ifcs.size()]),
new ManagedEntityManagerFactoryInvocationHandler(emf, this, plusOperations));
}
/**
* Subclasses must implement this method to create the EntityManagerFactory
* that will be returned by the <code>getObject()</code> 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.
* <p>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();
}
}
}
}

View File

@ -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}.
*
* <p>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 <code>Transaction.begin</code>
* method. Throws an InvalidIsolationLevelException if a non-default isolation
* level is set.
* <p>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 (<code>null</code>) 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 <code>beginTransaction</code>
* implementation does not require any cleanup.
* @see #beginTransaction
*/
public void cleanupTransaction(Object transactionData) {
}
/**
* This implementation always returns <code>null</code>,
* 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.
* <p>If the JPA implementation returns a Connection handle that it expects
* the application to close after use, the dialect implementation needs to invoke
* <code>Connection.close()</code> (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");
}
}

View File

@ -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.
*
* <p>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
* <code>EntityManagerFactory.createEntityManager(Map)</code> (if any).
* <p>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
* <code>EntityManagerFactory.createEntityManager(Map)</code> (if any).
* <p>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.
* <p>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.
* <p>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 <code>null</code> 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());
}
}

View File

@ -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}.
*
* <p>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 <code>null</code>)
*/
EntityManagerFactory getNativeEntityManagerFactory();
/**
* Return the underlying PersistenceProvider that the underlying
* EntityManagerFactory was created with.
* @return the PersistenceProvider used to create this EntityManagerFactory,
* or <code>null</code> 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 <code>null</code> 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 <code>null</code> if
* it is an unnamed default. If <code>getPersistenceUnitInfo()</code>
* returns non-null, the return type of <code>getPersistenceUnitName()</code>
* must be equal to the value returned by
* <code>PersistenceUnitInfo.getPersistenceUnitName()</code>.
* @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 <code>null</code> if not known
*/
DataSource getDataSource();
/**
* Return the (potentially vendor-specific) EntityManager interface
* that this factory's EntityManagers will implement.
* <p>A <code>null</code> return value suggests that autodetection is supposed
* to happen: either based on a target <code>EntityManager</code> instance
* or simply defaulting to <code>javax.persistence.EntityManager</code>.
*/
Class<? extends EntityManager> getEntityManagerInterface();
/**
* Return the vendor-specific JpaDialect implementation for this
* EntityManagerFactory, or <code>null</code> if not known.
*/
JpaDialect getJpaDialect();
/**
* Return the ClassLoader that the application's beans are loaded with.
* <p>Proxies will be generated in this ClassLoader.
*/
ClassLoader getBeanClassLoader();
}

View File

@ -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 {
}

View File

@ -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.
*
* <p>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 {
}

View File

@ -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.
*
* <p>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).
* <p>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.
* <p>Note: Will return <code>null</code> if no thread-bound EntityManager found!
* @param emf EntityManagerFactory to create the EntityManager with
* @return the EntityManager, or <code>null</code> 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.
* <p>Note: Will return <code>null</code> if no thread-bound EntityManager found!
* @param emf EntityManagerFactory to create the EntityManager with
* @param properties the properties to be passed into the <code>createEntityManager</code>
* call (may be <code>null</code>)
* @return the EntityManager, or <code>null</code> 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.
* <p>Same as <code>getEntityManager</code>, but throwing the original PersistenceException.
* @param emf EntityManagerFactory to create the EntityManager with
* @param properties the properties to be passed into the <code>createEntityManager</code>
* call (may be <code>null</code>)
* @return the EntityManager, or <code>null</code> 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
* <code>org.springframework.dao</code> hierarchy.
* Return null if no translation is appropriate: any other exception may
* have resulted from user code, and should not be translated.
* <p>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 <code>null</code> 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 <code>null</code>)
* @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);
}
}
}

View File

@ -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.
*
* <p>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;
}
}

View File

@ -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 {
}

View File

@ -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.
*
* <p>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 {
}

View File

@ -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.
*
* <p>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.
* <p>In case of an extended EntityManager, this will be the associated
* raw EntityManager.
* <p>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 <code>null</code>)
* @throws IllegalStateException if no underlying EntityManager is available
*/
EntityManager getTargetEntityManager() throws IllegalStateException;
}

View File

@ -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.
*
* <p>Supports explicit joining of a transaction through the
* <code>joinTransaction()</code> 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
* <code>joinTransaction()</code> 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 <code>null</code>)
* @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
* <code>joinTransaction()</code> 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 <code>null</code>)
* @param exceptionTranslator the exception translator to use for translating
* JPA commit/rollback exceptions during transaction synchronization
* (may be <code>null</code>)
* @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
* <code>joinTransaction()</code> 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 <code>null</code>)
* @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 <code>null</code>)
* @param exceptionTranslator the exception translator to use for translating
* JPA commit/rollback exceptions during transaction synchronization
* (may be <code>null</code>)
* @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 <code>createEntityManager</code>
* call (may be <code>null</code>)
* @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 <code>null</code> for default detection of all interfaces
* @param plusOperations an implementation of the EntityManagerPlusOperations
* interface, if those operations should be exposed (may be <code>null</code>)
* @param exceptionTranslator the PersistenceException translator to use
* @param jta whether to create a JTA-aware EntityManager
* (or <code>null</code> 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<Class> ifcs = new LinkedHashSet<Class>();
if (emIfc != null) {
ifcs.add(emIfc);
}
else {
ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(rawEm.getClass(), cl));
}
ifcs.add(EntityManagerProxy.class);
if (plusOperations != null) {
ifcs.add(EntityManagerPlusOperations.class);
}
return (EntityManager) Proxy.newProxyInstance(
(cl != null ? cl : ExtendedEntityManagerCreator.class.getClassLoader()),
ifcs.toArray(new Class[ifcs.size()]),
new ExtendedEntityManagerInvocationHandler(
rawEm, plusOperations, exceptionTranslator, jta, containerManaged));
}
/**
* InvocationHandler for extended EntityManagers as defined in the JPA spec.
*/
private static class ExtendedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
private static final Log logger = LogFactory.getLog(ExtendedEntityManagerInvocationHandler.class);
private final EntityManager target;
private final EntityManagerPlusOperations plusOperations;
private final PersistenceExceptionTranslator exceptionTranslator;
private final boolean containerManaged;
private boolean jta;
private ExtendedEntityManagerInvocationHandler(
EntityManager target, EntityManagerPlusOperations plusOperations,
PersistenceExceptionTranslator exceptionTranslator, Boolean jta, boolean containerManaged) {
this.target = target;
this.plusOperations = plusOperations;
this.exceptionTranslator = exceptionTranslator;
this.jta = (jta != null ? jta.booleanValue() : isJtaEntityManager());
this.containerManaged = containerManaged;
}
private boolean isJtaEntityManager() {
try {
this.target.getTransaction();
return false;
}
catch (IllegalStateException ex) {
logger.debug("Cannot access EntityTransaction handle - assuming we're in a JTA environment");
return true;
}
}
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("getTargetEntityManager")) {
return this.target;
}
else if (method.getName().equals("isOpen")) {
if (this.containerManaged) {
return true;
}
}
else if (method.getName().equals("close")) {
if (this.containerManaged) {
throw new IllegalStateException("Invalid usage: Cannot close a container-managed EntityManager");
}
}
else if (method.getName().equals("getTransaction")) {
if (this.containerManaged) {
throw new IllegalStateException(
"Cannot execute getTransaction() on a container-managed EntityManager");
}
}
else if (method.getName().equals("joinTransaction")) {
doJoinTransaction(true);
return null;
}
// Do automatic joining if required.
if (this.containerManaged && method.getDeclaringClass().isInterface()) {
doJoinTransaction(false);
}
// Invoke method on current EntityManager.
try {
if (method.getDeclaringClass().equals(EntityManagerPlusOperations.class)) {
return method.invoke(this.plusOperations, args);
}
else {
return method.invoke(this.target, args);
}
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
/**
* Join an existing transaction, if not already joined.
* @param enforce whether to enforce the transaction
* (i.e. whether failure to join is considered fatal)
*/
private void doJoinTransaction(boolean enforce) {
if (this.jta) {
// Let's try whether we're in a JTA transaction.
try {
this.target.joinTransaction();
logger.debug("Joined JTA transaction");
}
catch (TransactionRequiredException ex) {
if (!enforce) {
logger.debug("No JTA transaction to join: " + ex);
}
else {
throw ex;
}
}
}
else {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
if (!TransactionSynchronizationManager.hasResource(this.target) &&
!this.target.getTransaction().isActive()) {
enlistInCurrentTransaction();
}
logger.debug("Joined local transaction");
}
else {
if (!enforce) {
logger.debug("No local transaction to join");
}
else {
throw new TransactionRequiredException("No local transaction to join");
}
}
}
}
/**
* Enlist this application-managed EntityManager in the current transaction.
*/
private void enlistInCurrentTransaction() {
// Resource local transaction, need to acquire the EntityTransaction,
// start a transaction now and enlist a synchronization for
// commit or rollback later.
EntityTransaction et = this.target.getTransaction();
et.begin();
if (logger.isDebugEnabled()) {
logger.debug("Starting resource local transaction on application-managed " +
"EntityManager [" + this.target + "]");
}
ExtendedEntityManagerSynchronization extendedEntityManagerSynchronization =
new ExtendedEntityManagerSynchronization(this.target, this.exceptionTranslator);
TransactionSynchronizationManager.bindResource(this.target,
extendedEntityManagerSynchronization);
TransactionSynchronizationManager.registerSynchronization(extendedEntityManagerSynchronization);
}
}
/**
* TransactionSynchronization enlisting an extended EntityManager
* with a current Spring transaction.
*/
private static class ExtendedEntityManagerSynchronization extends ResourceHolderSynchronization
implements Ordered {
private final EntityManager entityManager;
private final PersistenceExceptionTranslator exceptionTranslator;
public ExtendedEntityManagerSynchronization(
EntityManager em, PersistenceExceptionTranslator exceptionTranslator) {
super(new EntityManagerHolder(em), em);
this.entityManager = em;
this.exceptionTranslator = exceptionTranslator;
}
public int getOrder() {
return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1;
}
protected boolean shouldReleaseBeforeCompletion() {
return false;
}
public void afterCommit() {
super.afterCommit();
// Trigger commit here to let exceptions propagate to the caller.
try {
this.entityManager.getTransaction().commit();
}
catch (RuntimeException ex) {
throw convertCompletionException(ex);
}
}
public void afterCompletion(int status) {
super.afterCompletion(status);
if (status != STATUS_COMMITTED) {
// Haven't had an afterCommit call: trigger a rollback.
try {
this.entityManager.getTransaction().rollback();
}
catch (RuntimeException ex) {
throw convertCompletionException(ex);
}
}
}
private RuntimeException convertCompletionException(RuntimeException ex) {
DataAccessException daex = (this.exceptionTranslator != null) ?
this.exceptionTranslator.translateExceptionIfPossible(ex) :
EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
return (daex != null ? daex : ex);
}
}
}

View File

@ -0,0 +1,155 @@
/*
* 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 javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.support.DataAccessUtils;
/**
* Base class for JpaTemplate and JpaInterceptor, defining common
* properties such as EntityManagerFactory and flushing behavior.
*
* <p>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.
* <p>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.
* <p>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.
* <p>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:
* <ul>
* <li>additional communication roundtrips with the database, instead of a
* single batch at transaction commit;
* <li>the fact that an actual database rollback is needed if the JPA
* transaction rolls back (due to already submitted SQL statements).
* </ul>
*/
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
* <code>org.springframework.dao</code> hierarchy if necessary, or
* return the exception itself if it is not persistence related
* <p>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());
}
}

View File

@ -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 <code>EntityManager.find/merge</code>
* 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 <code>JpaTemplate.execute</code> with an active
* JPA <code>EntityManager</code>. Does not need to care about activating
* or closing the <code>EntityManager</code>, or handling transactions.
*
* <p>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.
*
* <p>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 <code>null</code> 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;
}

View File

@ -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.
*
* <p>Also allows for the provision of value-added methods for portable yet
* more capable EntityManager and EntityManagerFactory subinterfaces offered
* by Spring.
*
* <p>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.
* <p>An implementation can configure the JPA Transaction object and then
* invoke <code>begin</code>, or invoke a special begin method that takes,
* for example, an isolation level.
* <p>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 <code>cleanupTransaction</code>.
* It may also apply the read-only flag and isolation level to the underlying
* JDBC Connection before beginning the transaction.
* <p>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).
* <p>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.
* <p>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 <code>cleanupTransaction</code>.
* <p>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.
* <p>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 <code>releaseJdbcConnection</code> method when not needed anymore.
* <p>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).
* <p>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.
* <p>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 <code>releaseJdbcConnection</code>,
* 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
* <code>releaseJdbcConnection</code>, or <code>null</code>
* 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 <code>getJdbcConnection</code>. This should be invoked in any case,
* to allow for proper release of the retrieved Connection handle.
* <p>An implementation might simply do nothing, if the Connection returned
* by <code>getJdbcConnection</code> 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;
}

View File

@ -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.
*
* <p>Application code must retrieve a JPA EntityManager via the
* <code>EntityManagerFactoryUtils.getEntityManager</code> method or - preferably -
* via a shared <code>EntityManager</code> reference, to be able to detect a
* thread-bound EntityManager. Typically, the code will look like as follows:
*
* <pre>
* public void doSomeDataAccessAction() {
* this.entityManager...
* }</pre>
*
* <p>Note that this interceptor automatically translates PersistenceExceptions,
* via delegating to the <code>EntityManagerFactoryUtils.convertJpaAccessException</code>
* method that converts them to exceptions that are compatible with the
* <code>org.springframework.dao</code> exception hierarchy (like JpaTemplate does).
*
* <p>This class can be considered a declarative alternative to JpaTemplate's
* callback approach. The advantages are:
* <ul>
* <li>no anonymous classes necessary for callback implementations;
* <li>the possibility to throw any application exceptions from within data access code.
* </ul>
*
* <p>The drawback is the dependency on interceptor configuration. However, note
* that this interceptor is usually <i>not</i> 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 <code>org.springframework.dao</code> exception hierarchy.
* <p>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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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.
*
* <p>Defines <code>JpaTemplate</code>'s data access methods that mirror
* various {@link javax.persistence.EntityManager} methods. Users are
* strongly encouraged to read the JPA <code>EntityManager</code>
* javadocs for details on the semantics of those methods.
*
* <p>Note that lazy loading will just work with an open JPA
* <code>EntityManager</code>, 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: <code>flush</code>, <code>clear</code>.
*
* @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;
<T> T find(Class<T> entityClass, Object id) throws DataAccessException;
<T> T getReference(Class<T> entityClass, Object id) throws DataAccessException;
boolean contains(Object entity) throws DataAccessException;
void refresh(Object entity) throws DataAccessException;
void persist(Object entity) throws DataAccessException;
<T> T merge(T entity) throws DataAccessException;
void remove(Object entity) throws DataAccessException;
void flush() throws DataAccessException;
List find(String queryString) throws DataAccessException;
List find(String queryString, Object... values) throws DataAccessException;
List findByNamedParams(String queryString, Map<String,? extends Object> params) throws DataAccessException;
List findByNamedQuery(String queryName) throws DataAccessException;
List findByNamedQuery(String queryName, Object... values) throws DataAccessException;
List findByNamedQueryAndNamedParams(String queryName, Map<String,? extends Object> params) throws DataAccessException;
}

View File

@ -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.OptimisticLockException;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
/**
* JPA-specific subclass of ObjectOptimisticLockingFailureException.
* Converts JPA's OptimisticLockException.
*
* @author Juergen Hoeller
* @since 2.0
* @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
*/
public class JpaOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
public JpaOptimisticLockingFailureException(OptimisticLockException ex) {
super(ex.getMessage(), ex);
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.PersistenceException;
import org.springframework.dao.UncategorizedDataAccessException;
/**
* JPA-specific subclass of UncategorizedDataAccessException,
* for JPA system errors that do not match any concrete
* <code>org.springframework.dao</code> exceptions.
*
* @author Juergen Hoeller
* @since 2.0
* @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
*/
public class JpaSystemException extends UncategorizedDataAccessException {
public JpaSystemException(PersistenceException ex) {
super(ex.getMessage(), ex);
}
}

View File

@ -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 <code>org.springframework.dao</code> exception hierarchy.
*
* <p>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.
*
* <p>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.
*
* <p><b>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.</b>
* (Using Spring's SharedEntityManagerBean / PersistenceAnnotationBeanPostProcessor,
* or using a direct JNDI lookup for an EntityManager on a Java EE 5 server.)
*
* <p>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}.
*
* <p>{@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 <code>close</code> calls and automatically applying transaction
* timeouts (if any).
* <p>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 <code>null</code>
* @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 <T> T find(final Class<T> entityClass, final Object id) throws DataAccessException {
return (T) execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
return em.find(entityClass, id);
}
}, true);
}
@SuppressWarnings("unchecked")
public <T> T getReference(final Class<T> entityClass, final Object id) throws DataAccessException {
return (T) execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
return em.getReference(entityClass, id);
}
}, true);
}
public boolean contains(final Object entity) throws DataAccessException {
Boolean result = (Boolean) execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
return new Boolean(em.contains(entity));
}
}, true);
return result.booleanValue();
}
public void refresh(final Object entity) throws DataAccessException {
execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
em.refresh(entity);
return null;
}
}, true);
}
public void persist(final Object entity) throws DataAccessException {
execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
em.persist(entity);
return null;
}
}, true);
}
@SuppressWarnings("unchecked")
public <T> T merge(final T entity) throws DataAccessException {
return (T) execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
return em.merge(entity);
}
}, true);
}
public void remove(final Object entity) throws DataAccessException {
execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
em.remove(entity);
return null;
}
}, true);
}
public void flush() throws DataAccessException {
execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
em.flush();
return null;
}
}, true);
}
//-------------------------------------------------------------------------
// Convenience finder methods
//-------------------------------------------------------------------------
public List find(String queryString) throws DataAccessException {
return find(queryString, (Object[]) null);
}
public List find(final String queryString, final Object... values) throws DataAccessException {
return executeFind(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query queryObject = em.createQuery(queryString);
if (values != null) {
for (int i = 0; i < values.length; i++) {
queryObject.setParameter(i + 1, values[i]);
}
}
return queryObject.getResultList();
}
});
}
public List findByNamedParams(final String queryString, final Map<String, ?> params) throws DataAccessException {
return executeFind(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query queryObject = em.createQuery(queryString);
if (params != null) {
for (Map.Entry<String, ?> entry : params.entrySet()) {
queryObject.setParameter(entry.getKey(), entry.getValue());
}
}
return queryObject.getResultList();
}
});
}
public List findByNamedQuery(String queryName) throws DataAccessException {
return findByNamedQuery(queryName, (Object[]) null);
}
public List findByNamedQuery(final String queryName, final Object... values) throws DataAccessException {
return executeFind(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query queryObject = em.createNamedQuery(queryName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
queryObject.setParameter(i + 1, values[i]);
}
}
return queryObject.getResultList();
}
});
}
public List findByNamedQueryAndNamedParams(final String queryName, final Map<String, ?> params)
throws DataAccessException {
return executeFind(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query queryObject = em.createNamedQuery(queryName);
if (params != null) {
for (Map.Entry<String, ?> entry : params.entrySet()) {
queryObject.setParameter(entry.getKey(), entry.getValue());
}
}
return queryObject.getResultList();
}
});
}
/**
* Invocation handler that suppresses close calls on JPA EntityManagers.
* Also prepares returned Query and Criteria objects.
* @see javax.persistence.EntityManager#close
*/
private class CloseSuppressingInvocationHandler implements InvocationHandler {
private final EntityManager target;
public CloseSuppressingInvocationHandler(EntityManager target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Invocation on EntityManager 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 EntityManager 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 EntityManager.
try {
return method.invoke(this.target, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
}

View File

@ -0,0 +1,652 @@
/*
* 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 javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
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.NestedTransactionNotSupportedException;
import org.springframework.transaction.SavepointManager;
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;
import org.springframework.util.CollectionUtils;
/**
* {@link org.springframework.transaction.PlatformTransactionManager} implementation
* for a single JPA {@link javax.persistence.EntityManagerFactory}. Binds a JPA
* EntityManager from the specified factory to the thread, potentially allowing for
* one thread-bound EntityManager per factory. {@link SharedEntityManagerCreator}
* and {@link JpaTemplate} are aware of thread-bound entity managers and participate
* in such transactions automatically. Using either is required for JPA access code
* supporting this transaction management mechanism.
*
* <p>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.
*
* <p>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.
*
* <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
* this instance needs to be aware of the DataSource ({@link #setDataSource}).
* The given DataSource should obviously match the one used by the given
* 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.
*
* <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
* Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
* flag defaults to "false", though, as nested transactions will just apply to the
* JDBC Connection, not to the 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). <i>Note that JPA itself does not support
* nested transactions! Hence, do not expect JPA access code to semantically
* participate in a nested transaction.</i>
*
* @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
* <code>EntityManagerFactory.createEntityManager(Map)</code> (if any).
* <p>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
* <code>EntityManagerFactory.createEntityManager(Map)</code> (if any).
* <p>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.
* <p>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.
* <p>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.
* <p>A transactional JDBC Connection for this DataSource will be provided to
* application code accessing this DataSource directly via DataSourceUtils
* or JdbcTemplate. The Connection will be taken from the JPA EntityManager.
* <p>Note that you need to use a JPA dialect for a specific JPA implementation
* to allow for exposing JPA transactions as JDBC transactions.
* <p>The DataSource specified here should be the target DataSource to manage
* transactions for, not a TransactionAwareDataSourceProxy. Only data access
* code may work with TransactionAwareDataSourceProxy, while the transaction
* manager needs to work on the underlying target DataSource. If there's
* nevertheless a TransactionAwareDataSourceProxy passed in, it will be
* unwrapped to extract its target DataSource.
* @see 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.
* <p>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.
* <p>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.
* <p>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 <code>doSuspend</code> and <code>doResume</code>.
*/
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;
}
}
}

View File

@ -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.
* <p>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 <code>null</code> 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 <code>null</code> if there is none.
*/
JpaDialect getJpaDialect();
/**
* Return the vendor-specific EntityManagerFactory interface
* that the EntityManagerFactory proxy is supposed to implement.
* <p>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.
* <p>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.
* <p>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);
}

Some files were not shown because too many files have changed in this diff Show More