Skip to content

Commit fa8d202

Browse files
committed
AspectJ support for javax.transaction.Transactional
Issue: SPR-11803
1 parent d75f390 commit fa8d202

File tree

5 files changed

+270
-3
lines changed

5 files changed

+270
-3
lines changed

build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ configure(allprojects) { project ->
4242
ext.jasperReportsVersion = "6.0.3"
4343
ext.jettyVersion = "9.2.7.v20150116"
4444
ext.jodaVersion = "2.6"
45+
ext.jtaVersion = "1.2"
4546
ext.junitVersion = "4.12"
4647
ext.nettyVersion = "4.0.25.Final"
4748
ext.openJpaVersion = "2.2.2" // 2.3.0 not Java 8 compatible (based on ASM 4)
@@ -517,7 +518,7 @@ project("spring-tx") {
517518
optional(project(":spring-aop"))
518519
optional(project(":spring-context")) // for JCA, @EnableTransactionManagement
519520
optional("aopalliance:aopalliance:1.0")
520-
optional("javax.transaction:javax.transaction-api:1.2")
521+
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
521522
optional("javax.resource:connector-api:1.5")
522523
optional("javax.ejb:ejb-api:3.0")
523524
optional("com.ibm.websphere:uow:6.0.2.17")
@@ -577,7 +578,7 @@ project("spring-jms") {
577578
provided("javax.jms:jms-api:1.1-rev-1")
578579
optional(project(":spring-oxm"))
579580
optional("aopalliance:aopalliance:1.0")
580-
optional("javax.transaction:javax.transaction-api:1.2")
581+
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
581582
optional("javax.resource:connector-api:1.5")
582583
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
583584
}
@@ -591,7 +592,7 @@ project("spring-jdbc") {
591592
compile(project(":spring-core"))
592593
compile(project(":spring-tx"))
593594
optional(project(":spring-context")) // for JndiDataSourceLookup
594-
optional("javax.transaction:javax.transaction-api:1.2")
595+
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
595596
optional("com.mchange:c3p0:0.9.2.1")
596597
optional("org.hsqldb:hsqldb:${hsqldbVersion}")
597598
optional("com.h2database:h2:1.4.182")
@@ -1035,6 +1036,7 @@ project("spring-aspects") {
10351036
optional(project(":spring-context-support")) // for JavaMail and JSR-107 support
10361037
optional(project(":spring-orm")) // for JPA exception translation support
10371038
optional(project(":spring-tx")) // for JPA, @Transactional support
1039+
optional("javax.transaction:javax.transaction-api:${jtaVersion}") // for @javax.transaction.Transactional support
10381040
optional("javax.cache:cache-api:1.0.0")
10391041
testCompile(project(":spring-core")) // for CodeStyleAspect
10401042
testCompile(project(":spring-test"))
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.transaction.aspectj;
18+
19+
import javax.transaction.Transactional;
20+
21+
import org.aspectj.lang.annotation.RequiredTypes;
22+
23+
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
24+
25+
/**
26+
* Concrete AspectJ transaction aspect using {@code javax.transaction.Transactional} annotation.
27+
*
28+
* <p>When using this aspect, you <i>must</i> annotate the implementation class
29+
* (and/or methods within that class), <i>not</i> the interface (if any) that
30+
* the class implements. AspectJ follows Java's rule that annotations on
31+
* interfaces are <i>not</i> inherited.
32+
*
33+
* <p>An @Transactional annotation on a class specifies the default transaction
34+
* semantics for the execution of any <b>public</b> operation in the class.
35+
*
36+
* <p>An @Transactional annotation on a method within the class overrides the
37+
* default transaction semantics given by the class annotation (if present).
38+
* Any method may be annotated (regardless of visibility). Annotating
39+
* non-public methods directly is the only way to get transaction demarcation
40+
* for the execution of such operations.
41+
*
42+
* @author Stephane Nicoll
43+
* @since 4.2
44+
* @see Transactional
45+
* @see AnnotationTransactionAspect
46+
*/
47+
@RequiredTypes({"javax.transaction.Transactional"})
48+
public aspect JtaAnnotationTransactionAspect extends AbstractTransactionAspect {
49+
50+
public JtaAnnotationTransactionAspect() {
51+
super(new AnnotationTransactionAttributeSource(false));
52+
}
53+
54+
/**
55+
* Matches the execution of any public method in a type with the Transactional
56+
* annotation, or any subtype of a type with the Transactional annotation.
57+
*/
58+
private pointcut executionOfAnyPublicMethodInAtTransactionalType() :
59+
execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *);
60+
61+
/**
62+
* Matches the execution of any method with the Transactional annotation.
63+
*/
64+
private pointcut executionOfTransactionalMethod() :
65+
execution(@Transactional * *(..));
66+
67+
/**
68+
* Definition of pointcut from super aspect - matched join points
69+
* will have Spring transaction management applied.
70+
*/
71+
protected pointcut transactionalMethodExecution(Object txObject) :
72+
(executionOfAnyPublicMethodInAtTransactionalType()
73+
|| executionOfTransactionalMethod() )
74+
&& this(txObject);
75+
76+
}

spring-aspects/src/main/resources/META-INF/aop.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>
1414
<aspect name="org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect"/>
1515
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
16+
<aspect name="org.springframework.transaction.aspectj.JtaAnnotationTransactionAspect"/>
1617
<aspect name="org.springframework.cache.aspectj.AnnotationCacheAspect"/>
1718
<aspect name="org.springframework.cache.aspectj.JCacheCacheAspect"/>
1819
</aspects>
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.transaction.aspectj;
18+
19+
import java.io.IOException;
20+
21+
import javax.transaction.Transactional;
22+
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.test.context.ContextConfiguration;
31+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
32+
import org.springframework.tests.transaction.CallCountingTransactionManager;
33+
34+
import static org.junit.Assert.*;
35+
36+
/**
37+
* @author Stephane Nicoll
38+
*/
39+
@RunWith(SpringJUnit4ClassRunner.class)
40+
@ContextConfiguration(classes = JtaTransactionAspectsTests.Config.class)
41+
public class JtaTransactionAspectsTests {
42+
43+
@Autowired
44+
private CallCountingTransactionManager txManager;
45+
46+
@Before
47+
public void setUp() {
48+
this.txManager.clear();
49+
}
50+
51+
@Test
52+
public void commitOnAnnotatedPublicMethod() throws Throwable {
53+
assertEquals(0, this.txManager.begun);
54+
new JtaAnnotationPublicAnnotatedMember().echo(null);
55+
assertEquals(1, this.txManager.commits);
56+
}
57+
58+
@Test
59+
public void matchingRollbackOnApplied() throws Throwable {
60+
assertEquals(0, this.txManager.begun);
61+
InterruptedException test = new InterruptedException();
62+
try {
63+
new JtaAnnotationPublicAnnotatedMember().echo(test);
64+
fail("Should have thrown an exception");
65+
}
66+
catch (Throwable throwable) {
67+
assertEquals("wrong exception", test, throwable);
68+
}
69+
assertEquals(1, this.txManager.rollbacks);
70+
assertEquals(0, this.txManager.commits);
71+
}
72+
73+
@Test
74+
public void nonMatchingRollbackOnApplied() throws Throwable {
75+
assertEquals(0, this.txManager.begun);
76+
IOException test = new IOException();
77+
try {
78+
new JtaAnnotationPublicAnnotatedMember().echo(test);
79+
fail("Should have thrown an exception");
80+
}
81+
catch (Throwable throwable) {
82+
assertEquals("wrong exception", test, throwable);
83+
}
84+
assertEquals(1, this.txManager.commits);
85+
assertEquals(0, this.txManager.rollbacks);
86+
}
87+
88+
@Test
89+
public void commitOnAnnotatedProtectedMethod() {
90+
assertEquals(0, this.txManager.begun);
91+
new JtaAnnotationProtectedAnnotatedMember().doInTransaction();
92+
assertEquals(1, this.txManager.commits);
93+
}
94+
95+
@Test
96+
public void nonAnnotatedMethodCallingProtectedMethod() {
97+
assertEquals(0, this.txManager.begun);
98+
new JtaAnnotationProtectedAnnotatedMember().doSomething();
99+
assertEquals(1, this.txManager.commits);
100+
}
101+
102+
@Test
103+
public void commitOnAnnotatedPrivateMethod() {
104+
assertEquals(0, this.txManager.begun);
105+
new JtaAnnotationPrivateAnnotatedMember().doInTransaction();
106+
assertEquals(1, this.txManager.commits);
107+
}
108+
109+
@Test
110+
public void nonAnnotatedMethodCallingPrivateMethod() {
111+
assertEquals(0, this.txManager.begun);
112+
new JtaAnnotationPrivateAnnotatedMember().doSomething();
113+
assertEquals(1, this.txManager.commits);
114+
}
115+
116+
@Test
117+
public void notTransactional() {
118+
assertEquals(0, this.txManager.begun);
119+
new TransactionAspectTests.NotTransactional().noop();
120+
assertEquals(0, this.txManager.begun);
121+
}
122+
123+
124+
public static class JtaAnnotationPublicAnnotatedMember {
125+
126+
@Transactional(rollbackOn = InterruptedException.class)
127+
public void echo(Throwable t) throws Throwable {
128+
if (t != null) {
129+
throw t;
130+
}
131+
}
132+
133+
}
134+
135+
protected static class JtaAnnotationProtectedAnnotatedMember {
136+
137+
public void doSomething() {
138+
doInTransaction();
139+
}
140+
141+
@Transactional
142+
protected void doInTransaction() {
143+
}
144+
}
145+
146+
protected static class JtaAnnotationPrivateAnnotatedMember {
147+
148+
public void doSomething() {
149+
doInTransaction();
150+
}
151+
152+
@Transactional
153+
private void doInTransaction() {
154+
}
155+
}
156+
157+
@Configuration
158+
protected static class Config {
159+
160+
@Bean
161+
public CallCountingTransactionManager transactionManager() {
162+
return new CallCountingTransactionManager();
163+
}
164+
165+
@Bean
166+
public JtaAnnotationTransactionAspect transactionAspect() {
167+
JtaAnnotationTransactionAspect aspect = JtaAnnotationTransactionAspect.aspectOf();
168+
aspect.setTransactionManager(transactionManager());
169+
return aspect;
170+
}
171+
172+
}
173+
174+
}

src/asciidoc/index.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16060,6 +16060,13 @@ transaction semantics given by the class annotation (if present). Methods with `
1606016060
default visibility methods directly is the only way to get transaction demarcation for
1606116061
the execution of such methods.
1606216062

16063+
[TIP]
16064+
====
16065+
Since Spring Framework 4.2, `spring-aspects` provides a similar aspect that offers the
16066+
exact same features for the standard `javax.transaction.Transactional` annotation. Check
16067+
`JtaAnnotationTransactionAspect` for more details.
16068+
====
16069+
1606316070
For AspectJ programmers that want to use the Spring configuration and transaction
1606416071
management support but don't want to (or cannot) use annotations, `spring-aspects.jar`
1606516072
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
2376223769
danger of undue coupling, because code that is meant to be used transactionally is
2376323770
almost always deployed that way anyway.
2376423771

23772+
[NOTE]
23773+
====
23774+
The standard `javax.transaction.Transactional` annotation is also supported as a drop-in
23775+
replacement to Spring's own annotation. Please refer to JTA 1.2 documentation for more
23776+
details.
23777+
====
23778+
2376523779
The ease-of-use afforded by the use of the `@Transactional` annotation is best
2376623780
illustrated with an example, which is explained in the text that follows. Consider the
2376723781
following class definition:

0 commit comments

Comments
 (0)