Initial import of ORM
This commit is contained in:
parent
c236f9fac7
commit
d1061e7e9f
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
* <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
|
||||||
|
* ...
|
||||||
|
* <property name="filterDefinitions">
|
||||||
|
* <list>
|
||||||
|
* <bean class="org.springframework.orm.hibernate3.FilterDefinitionFactoryBean">
|
||||||
|
* <property name="filterName" value="myFilter"/>
|
||||||
|
* <property name="parameterTypes">
|
||||||
|
* <props>
|
||||||
|
* <prop key="myParam">string</prop>
|
||||||
|
* <prop key="myOtherParam">long</prop>
|
||||||
|
* </props>
|
||||||
|
* </property>
|
||||||
|
* </bean>
|
||||||
|
* </list>
|
||||||
|
* </property>
|
||||||
|
* ...
|
||||||
|
* </bean></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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
* <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
|
||||||
|
* ...
|
||||||
|
* <property name="typeDefinitions">
|
||||||
|
* <list>
|
||||||
|
* <bean class="org.springframework.orm.hibernate3.TypeDefinitionBean">
|
||||||
|
* <property name="typeName" value="myType"/>
|
||||||
|
* <property name="typeClass" value="mypackage.MyTypeClass"/>
|
||||||
|
* </bean>
|
||||||
|
* </list>
|
||||||
|
* </property>
|
||||||
|
* ...
|
||||||
|
* </bean></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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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">
|
||||||
|
* <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
|
||||||
|
* <property name="dataSource" ref="dataSource"/>
|
||||||
|
* <property name="annotatedClasses">
|
||||||
|
* <list>
|
||||||
|
* <value>test.package.Foo</value>
|
||||||
|
* <value>test.package.Bar</value>
|
||||||
|
* </list>
|
||||||
|
* </property>
|
||||||
|
* </bean></pre>
|
||||||
|
*
|
||||||
|
* Or when using classpath scanning for autodetection of entity classes:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
|
||||||
|
* <property name="dataSource" ref="dataSource"/>
|
||||||
|
* <property name="packagesToScan" value="test.package"/>
|
||||||
|
* </bean></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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
* <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
|
||||||
|
* ...
|
||||||
|
* <property name="entityInterceptor">
|
||||||
|
* <bean class="org.springframework.orm.hibernate3.support.ScopedBeanInterceptor"/>
|
||||||
|
* </property>
|
||||||
|
* </bean></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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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 "<sqlMap>" entries
|
||||||
|
* in a sql-map-client config file. This property being based on Spring's
|
||||||
|
* resource abstraction also allows for specifying resource patterns here:
|
||||||
|
* e.g. "/myApp/*-map.xml".
|
||||||
|
* <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><properties></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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
* <bean id="persistenceManagerFactory" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close">
|
||||||
|
* <property name="connectionFactory" ref="dataSource"/>
|
||||||
|
* <property name="nontransactionalRead" value="true"/>
|
||||||
|
* </bean>
|
||||||
|
* </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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
Loading…
Reference in New Issue