diff --git a/pom.xml b/pom.xml
index 8e3fe85f34..148be495c1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -353,6 +353,12 @@
SpringSource Maven Release Repositoryhttp://repository.springsource.com/maven/bundles/release
+
+
+ jboss
+ JBoss repository
+ https://repository.jboss.org/nexus/content/groups/public
+
diff --git a/spring-data-commons-core/pom.xml b/spring-data-commons-core/pom.xml
index 9ce8a95658..5571a3b755 100644
--- a/spring-data-commons-core/pom.xml
+++ b/spring-data-commons-core/pom.xml
@@ -94,6 +94,14 @@
${querydsl.version}provided
+
+
+
+ javax.ejb
+ ejb-api
+ 3.0
+ true
+
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java
index 7e0a12db72..b44dbd2bee 100644
--- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java
@@ -15,12 +15,32 @@
*/
package org.springframework.data.repository.support;
+import java.io.Serializable;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.core.BridgeMethodResolver;
import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor;
-import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
+import org.springframework.transaction.annotation.Ejb3TransactionAnnotationParser;
+import org.springframework.transaction.annotation.SpringTransactionAnnotationParser;
+import org.springframework.transaction.annotation.TransactionAnnotationParser;
+import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
+import org.springframework.transaction.interceptor.TransactionAttribute;
+import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
/**
@@ -52,7 +72,7 @@ public TransactionalRepositoryProxyPostProcessor(
this.transactionInterceptor =
new TransactionInterceptor(null,
- new AnnotationTransactionAttributeSource());
+ new CustomAnnotationTransactionAttributeSource());
this.transactionInterceptor
.setTransactionManagerBeanName(transactionManagerName);
this.transactionInterceptor.setBeanFactory(beanFactory);
@@ -72,4 +92,346 @@ public void postProcess(ProxyFactory factory) {
factory.addAdvice(petInterceptor);
factory.addAdvice(transactionInterceptor);
}
+
+ // The section below contains copies of two core Spring classes that slightly modify the algorithm transaction
+ // configuration is discovered. The original Spring implementation favours the implementation class' transaction
+ // configuration over one declared at an interface. As we need to provide the capability to override transaction
+ // configuration of the implementation at the interface level we pretty much invert this logic to inspect the
+ // originally invoked method first before digging down into the implementation class.
+ //
+ // Unfortunately the Spring classes do not allow modifying this algorithm easily. That's why we have to copy the two
+ // classes 1:1. Only modifications done are inside
+ // AbstractFallbackTransactionAttributeSource#computeTransactionAttribute(Method, Class>).
+
+
+ /**
+ * Implementation of the
+ * {@link org.springframework.transaction.interceptor.TransactionAttributeSource}
+ * interface for working with transaction metadata in JDK 1.5+ annotation format.
+ *
+ *
This class reads Spring's JDK 1.5+ {@link Transactional} annotation and
+ * exposes corresponding transaction attributes to Spring's transaction infrastructure.
+ * Also supports EJB3's {@link javax.ejb.TransactionAttribute} annotation (if present).
+ * This class may also serve as base class for a custom TransactionAttributeSource,
+ * or get customized through {@link TransactionAnnotationParser} strategies.
+ *
+ * @author Colin Sampaleanu
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see Transactional
+ * @see TransactionAnnotationParser
+ * @see SpringTransactionAnnotationParser
+ * @see Ejb3TransactionAnnotationParser
+ * @see org.springframework.transaction.interceptor.TransactionInterceptor#setTransactionAttributeSource
+ * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean#setTransactionAttributeSource
+ */
+ static class CustomAnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource
+ implements Serializable {
+
+ private static final long serialVersionUID = 4841944452113159864L;
+ private static final boolean ejb3Present = ClassUtils.isPresent(
+ "javax.ejb.TransactionAttribute", CustomAnnotationTransactionAttributeSource.class.getClassLoader());
+
+ private final boolean publicMethodsOnly;
+ private final Set annotationParsers;
+
+
+ /**
+ * Create a default AnnotationTransactionAttributeSource, supporting
+ * public methods that carry the Transactional annotation
+ * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation.
+ */
+ public CustomAnnotationTransactionAttributeSource() {
+ this(true);
+ }
+
+ /**
+ * Create a custom AnnotationTransactionAttributeSource, supporting
+ * public methods that carry the Transactional annotation
+ * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation.
+ * @param publicMethodsOnly whether to support public methods that carry
+ * the Transactional annotation only (typically for use
+ * with proxy-based AOP), or protected/private methods as well
+ * (typically used with AspectJ class weaving)
+ */
+ public CustomAnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
+ this.publicMethodsOnly = publicMethodsOnly;
+ this.annotationParsers = new LinkedHashSet(2);
+ this.annotationParsers.add(new SpringTransactionAnnotationParser());
+ if (ejb3Present) {
+ this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
+ }
+ }
+
+ /**
+ * Create a custom AnnotationTransactionAttributeSource.
+ * @param annotationParser the TransactionAnnotationParser to use
+ */
+ public CustomAnnotationTransactionAttributeSource(TransactionAnnotationParser annotationParser) {
+ this.publicMethodsOnly = true;
+ Assert.notNull(annotationParser, "TransactionAnnotationParser must not be null");
+ this.annotationParsers = Collections.singleton(annotationParser);
+ }
+
+ /**
+ * Create a custom AnnotationTransactionAttributeSource.
+ * @param annotationParsers the TransactionAnnotationParsers to use
+ */
+ public CustomAnnotationTransactionAttributeSource(Set annotationParsers) {
+ this.publicMethodsOnly = true;
+ Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified");
+ this.annotationParsers = annotationParsers;
+ }
+
+
+ @Override
+ protected TransactionAttribute findTransactionAttribute(Method method) {
+ return determineTransactionAttribute(method);
+ }
+
+ @Override
+ protected TransactionAttribute findTransactionAttribute(Class> clazz) {
+ return determineTransactionAttribute(clazz);
+ }
+
+ /**
+ * Determine the transaction attribute for the given method or class.
+ *
This implementation delegates to configured
+ * {@link TransactionAnnotationParser TransactionAnnotationParsers}
+ * for parsing known annotations into Spring's metadata attribute class.
+ * Returns null if it's not transactional.
+ *
Can be overridden to support custom annotations that carry transaction metadata.
+ * @param ae the annotated method or class
+ * @return TransactionAttribute the configured transaction attribute,
+ * or null if none was found
+ */
+ protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {
+ for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
+ TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);
+ if (attr != null) {
+ return attr;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * By default, only public methods can be made transactional.
+ */
+ @Override
+ protected boolean allowPublicMethodsOnly() {
+ return this.publicMethodsOnly;
+ }
+ }
+
+ /**
+ * Abstract implementation of {@link TransactionAttributeSource} that caches
+ * attributes for methods and implements a fallback policy: 1. specific target
+ * method; 2. target class; 3. declaring method; 4. declaring class/interface.
+ *
+ *
Defaults to using the target class's transaction attribute if none is
+ * associated with the target method. Any transaction attribute associated with
+ * the target method completely overrides a class transaction attribute.
+ * If none found on the target class, the interface that the invoked method
+ * has been called through (in case of a JDK proxy) will be checked.
+ *
+ *
This implementation caches attributes by method after they are first used.
+ * If it is ever desirable to allow dynamic changing of transaction attributes
+ * (which is very unlikely), caching could be made configurable. Caching is
+ * desirable because of the cost of evaluating rollback rules.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 1.1
+ */
+ abstract static class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
+
+ /**
+ * Canonical value held in cache to indicate no transaction attribute was
+ * found for this method, and we don't need to look again.
+ */
+ private final static TransactionAttribute NULL_TRANSACTION_ATTRIBUTE = new DefaultTransactionAttribute();
+
+
+ /**
+ * Logger available to subclasses.
+ *
As this base class is not marked Serializable, the logger will be recreated
+ * after serialization - provided that the concrete subclass is Serializable.
+ */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /**
+ * Cache of TransactionAttributes, keyed by DefaultCacheKey (Method + target Class).
+ *
As this base class is not marked Serializable, the cache will be recreated
+ * after serialization - provided that the concrete subclass is Serializable.
+ */
+ final Map