diff --git a/build.gradle b/build.gradle index bbce836ac4..8cd3390a9d 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ configure(allprojects) { project -> ext.jasperReportsVersion = "6.0.3" ext.jettyVersion = "9.2.7.v20150116" ext.jodaVersion = "2.6" + ext.jtaVersion = "1.2" ext.junitVersion = "4.12" ext.nettyVersion = "4.0.25.Final" ext.openJpaVersion = "2.2.2" // 2.3.0 not Java 8 compatible (based on ASM 4) @@ -517,7 +518,7 @@ project("spring-tx") { optional(project(":spring-aop")) optional(project(":spring-context")) // for JCA, @EnableTransactionManagement optional("aopalliance:aopalliance:1.0") - optional("javax.transaction:javax.transaction-api:1.2") + optional("javax.transaction:javax.transaction-api:${jtaVersion}") optional("javax.resource:connector-api:1.5") optional("javax.ejb:ejb-api:3.0") optional("com.ibm.websphere:uow:6.0.2.17") @@ -577,7 +578,7 @@ project("spring-jms") { provided("javax.jms:jms-api:1.1-rev-1") optional(project(":spring-oxm")) optional("aopalliance:aopalliance:1.0") - optional("javax.transaction:javax.transaction-api:1.2") + optional("javax.transaction:javax.transaction-api:${jtaVersion}") optional("javax.resource:connector-api:1.5") optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}") } @@ -591,7 +592,7 @@ project("spring-jdbc") { compile(project(":spring-core")) compile(project(":spring-tx")) optional(project(":spring-context")) // for JndiDataSourceLookup - optional("javax.transaction:javax.transaction-api:1.2") + optional("javax.transaction:javax.transaction-api:${jtaVersion}") optional("com.mchange:c3p0:0.9.2.1") optional("org.hsqldb:hsqldb:${hsqldbVersion}") optional("com.h2database:h2:1.4.182") @@ -1035,6 +1036,7 @@ project("spring-aspects") { optional(project(":spring-context-support")) // for JavaMail and JSR-107 support optional(project(":spring-orm")) // for JPA exception translation support optional(project(":spring-tx")) // for JPA, @Transactional support + optional("javax.transaction:javax.transaction-api:${jtaVersion}") // for @javax.transaction.Transactional support optional("javax.cache:cache-api:1.0.0") testCompile(project(":spring-core")) // for CodeStyleAspect testCompile(project(":spring-test")) diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj new file mode 100644 index 0000000000..86a8557cba --- /dev/null +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.transaction.aspectj; + +import javax.transaction.Transactional; + +import org.aspectj.lang.annotation.RequiredTypes; + +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; + +/** + * Concrete AspectJ transaction aspect using {@code javax.transaction.Transactional} annotation. + * + *

When using this aspect, you must annotate the implementation class + * (and/or methods within that class), not the interface (if any) that + * the class implements. AspectJ follows Java's rule that annotations on + * interfaces are not inherited. + * + *

An @Transactional annotation on a class specifies the default transaction + * semantics for the execution of any public operation in the class. + * + *

An @Transactional annotation on a method within the class overrides the + * default transaction semantics given by the class annotation (if present). + * Any method may be annotated (regardless of visibility). Annotating + * non-public methods directly is the only way to get transaction demarcation + * for the execution of such operations. + * + * @author Stephane Nicoll + * @since 4.2 + * @see Transactional + * @see AnnotationTransactionAspect + */ +@RequiredTypes({"javax.transaction.Transactional"}) +public aspect JtaAnnotationTransactionAspect extends AbstractTransactionAspect { + + public JtaAnnotationTransactionAspect() { + super(new AnnotationTransactionAttributeSource(false)); + } + + /** + * Matches the execution of any public method in a type with the Transactional + * annotation, or any subtype of a type with the Transactional annotation. + */ + private pointcut executionOfAnyPublicMethodInAtTransactionalType() : + execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *); + + /** + * Matches the execution of any method with the Transactional annotation. + */ + private pointcut executionOfTransactionalMethod() : + execution(@Transactional * *(..)); + + /** + * Definition of pointcut from super aspect - matched join points + * will have Spring transaction management applied. + */ + protected pointcut transactionalMethodExecution(Object txObject) : + (executionOfAnyPublicMethodInAtTransactionalType() + || executionOfTransactionalMethod() ) + && this(txObject); + +} diff --git a/spring-aspects/src/main/resources/META-INF/aop.xml b/spring-aspects/src/main/resources/META-INF/aop.xml index f094856d4c..ad4f685d72 100644 --- a/spring-aspects/src/main/resources/META-INF/aop.xml +++ b/spring-aspects/src/main/resources/META-INF/aop.xml @@ -13,6 +13,7 @@ + diff --git a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/JtaTransactionAspectsTests.java b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/JtaTransactionAspectsTests.java new file mode 100644 index 0000000000..a4eea3e1e6 --- /dev/null +++ b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/JtaTransactionAspectsTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.transaction.aspectj; + +import java.io.IOException; + +import javax.transaction.Transactional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.tests.transaction.CallCountingTransactionManager; + +import static org.junit.Assert.*; + +/** + * @author Stephane Nicoll + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = JtaTransactionAspectsTests.Config.class) +public class JtaTransactionAspectsTests { + + @Autowired + private CallCountingTransactionManager txManager; + + @Before + public void setUp() { + this.txManager.clear(); + } + + @Test + public void commitOnAnnotatedPublicMethod() throws Throwable { + assertEquals(0, this.txManager.begun); + new JtaAnnotationPublicAnnotatedMember().echo(null); + assertEquals(1, this.txManager.commits); + } + + @Test + public void matchingRollbackOnApplied() throws Throwable { + assertEquals(0, this.txManager.begun); + InterruptedException test = new InterruptedException(); + try { + new JtaAnnotationPublicAnnotatedMember().echo(test); + fail("Should have thrown an exception"); + } + catch (Throwable throwable) { + assertEquals("wrong exception", test, throwable); + } + assertEquals(1, this.txManager.rollbacks); + assertEquals(0, this.txManager.commits); + } + + @Test + public void nonMatchingRollbackOnApplied() throws Throwable { + assertEquals(0, this.txManager.begun); + IOException test = new IOException(); + try { + new JtaAnnotationPublicAnnotatedMember().echo(test); + fail("Should have thrown an exception"); + } + catch (Throwable throwable) { + assertEquals("wrong exception", test, throwable); + } + assertEquals(1, this.txManager.commits); + assertEquals(0, this.txManager.rollbacks); + } + + @Test + public void commitOnAnnotatedProtectedMethod() { + assertEquals(0, this.txManager.begun); + new JtaAnnotationProtectedAnnotatedMember().doInTransaction(); + assertEquals(1, this.txManager.commits); + } + + @Test + public void nonAnnotatedMethodCallingProtectedMethod() { + assertEquals(0, this.txManager.begun); + new JtaAnnotationProtectedAnnotatedMember().doSomething(); + assertEquals(1, this.txManager.commits); + } + + @Test + public void commitOnAnnotatedPrivateMethod() { + assertEquals(0, this.txManager.begun); + new JtaAnnotationPrivateAnnotatedMember().doInTransaction(); + assertEquals(1, this.txManager.commits); + } + + @Test + public void nonAnnotatedMethodCallingPrivateMethod() { + assertEquals(0, this.txManager.begun); + new JtaAnnotationPrivateAnnotatedMember().doSomething(); + assertEquals(1, this.txManager.commits); + } + + @Test + public void notTransactional() { + assertEquals(0, this.txManager.begun); + new TransactionAspectTests.NotTransactional().noop(); + assertEquals(0, this.txManager.begun); + } + + + public static class JtaAnnotationPublicAnnotatedMember { + + @Transactional(rollbackOn = InterruptedException.class) + public void echo(Throwable t) throws Throwable { + if (t != null) { + throw t; + } + } + + } + + protected static class JtaAnnotationProtectedAnnotatedMember { + + public void doSomething() { + doInTransaction(); + } + + @Transactional + protected void doInTransaction() { + } + } + + protected static class JtaAnnotationPrivateAnnotatedMember { + + public void doSomething() { + doInTransaction(); + } + + @Transactional + private void doInTransaction() { + } + } + + @Configuration + protected static class Config { + + @Bean + public CallCountingTransactionManager transactionManager() { + return new CallCountingTransactionManager(); + } + + @Bean + public JtaAnnotationTransactionAspect transactionAspect() { + JtaAnnotationTransactionAspect aspect = JtaAnnotationTransactionAspect.aspectOf(); + aspect.setTransactionManager(transactionManager()); + return aspect; + } + + } + +} diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 4d0d467652..ba28fe0445 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -16060,6 +16060,13 @@ transaction semantics given by the class annotation (if present). Methods with ` default visibility methods directly is the only way to get transaction demarcation for the execution of such methods. +[TIP] +==== +Since Spring Framework 4.2, `spring-aspects` provides a similar aspect that offers the +exact same features for the standard `javax.transaction.Transactional` annotation. Check +`JtaAnnotationTransactionAspect` for more details. +==== + For AspectJ programmers that want to use the Spring configuration and transaction management support but don't want to (or cannot) use annotations, `spring-aspects.jar` also contains `abstract` aspects you can extend to provide your own pointcut @@ -23762,6 +23769,13 @@ source code puts the declarations much closer to the affected code. There is not danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway. +[NOTE] +==== +The standard `javax.transaction.Transactional` annotation is also supported as a drop-in +replacement to Spring's own annotation. Please refer to JTA 1.2 documentation for more +details. +==== + The ease-of-use afforded by the use of the `@Transactional` annotation is best illustrated with an example, which is explained in the text that follows. Consider the following class definition: