Synopsis of the original question: Using standard Spring Transactions with AOP proxying, it is not possible to call an @Transactional-marked method from a non-@Transactional-marked method in the same class and be within a transaction (specifically due to the aforementioned proxy). This is supposedly possible with Spring Transactions in AspectJ mode, but how is it done?
Edit: The full rundown for Spring Transactions in AspectJ mode using Load-Time Weaving:
Add the following to META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
<context:load-time-weaver />
(I'll assume you already have an AnnotationSessionFactoryBean
and a HibernateTransactionManager
set up in the application context. You can add transaction-manager="transactionManager"
as an attribute to your <tx:annotation-driven />
tag, but if the value of your transaction manager bean's id
attribute is actually "transactionManager
", then it's redundant, as "transactionManager
" is that attribute's default value.)
Add META-INF/aop.xml
. Contents are as follows:
<aspectj>
<aspects>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
</aspects>
<weaver>
<include within="my.package..*" /><!--Whatever your package space is.-->
</weaver>
</aspectj>
Add aspectjweaver-1.7.0.jar
and spring-aspects-3.1.2.RELEASE.jar
to your classpath
. I use Maven as my build tool, so here are the <dependency />
declarations for your project's POM.xml
file:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
spring-instrument-3.1.2.RELEASE.jar
is not needed as a <dependency />
on your classpath
, but you still need it somewhere so that you can point at it with the -javaagent
JVM flag, as follows:
-javaagent:fullpathofspring-instrument-3.1.2.RELEASE.jar
I'm working in Eclipse Juno, so to set this I went to Window -> Preferences -> Java -> Installed JREs. Then I clicked on the checked JRE in the list box and clicked the "Edit..." button to the right of the list box. The third text box in the resulting popup window is labeled "Default VM arguments:". This is where the -javaagent
flag should be typed or copy+pasted in.
Now for my actual test code classes. First, my main class, TestMain.java
:
package my.package;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");
TestClass testClass = applicationContext.getBean(TestClass.class);
testClass.nonTransactionalMethod();
}
}
And then my transactional class, TestClass.java
:
package my.package;
import my.package.TestDao;
import my.package.TestObject;
import org.springframework.transaction.annotation.Transactional;
public void TestClass {
private TestDao testDao;
public void setTestDao(TestDao testDao) {
this.testDao = testDao;
}
public TestDao getTestDao() {
return testDao;
}
public void nonTransactionalMethod() {
transactionalMethod();
}
@Transactional
private void transactionalMethod() {
TestObject testObject = new TestObject();
testObject.setId(1L);
testDao.save(testObject);
}
}
The trick here is that if the TestClass
is a field in TestMain
its class will be loaded by the ClassLoader
before the application context is loaded. Since the weaving is at the load-time of the class, and this weaving is done by Spring through the application context, it won't get woven because the class is already loaded before the application context is loaded and aware of it.
The further particulars of TestObject
and TestDao
are unimportant. Assume they are wired up with JPA and Hibernate annotations and use Hibernate for persistence (because they are, and they do), and that all the requisite <bean />
's are set up in the application context file.
Edit: The full rundown for Spring Transactions in AspectJ mode using Compile-Time Weaving:
Add the following to META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
(I'll assume you already have an AnnotationSessionFactoryBean
and a HibernateTransactionManager
set up in the application context. You can add transaction-manager="transactionManager"
as an attribute to your <tx:annotation-driven />
tag, but if the value of your transaction manager bean's id
attribute is actually "transactionManager
", then it's redundant, as "transactionManager
" is that attribute's default value.)
Add spring-aspects-3.1.2.RELEASE.jar
and aspectjrt-1.7.0.jar
to your classpath
. I use Maven as my build tool, so here's the <dependency />
declarations for the POM.xml
file:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.0</version>
</dependency>
In Eclipse Juno: Help -> Eclipse Marketplace -> text box labeled "Find:" -> type "ajdt" -> hit [Enter] -> "AspectJ Development Tools (Juno)" -> Install -> Etc.
After restarting Eclipse (it will make you), right-click your project to bring up the context menu. Look near the bottom: Configure -> Convert to AspectJ Project.
Add the following <plugin />
declaration in your POM.xml
(again with the Maven!):
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
Alternative: Right-click your project to bring up the context menu. Look near the bottom: AspectJ Tools -> Configure AspectJ Build Path -> Aspect Path tab -> press "Add External JARs..." -> locate the full/path/of/spring-aspects-3.1.2.RELEASE.jar
-> press "Open" -> press "OK".
If you took the Maven route, the <plugin />
above should be freaking out. To fix this: Help -> Install New Software... -> press "Add..." -> type whatever you like in the text box labeled "Name:" -> type or copy+paste http://dist.springsource.org/release/AJDT/configurator/
in the text box labeled "Location:" -> press "OK" -> Wait a second -> check the parent checkbox next to "Maven Integration for Eclipse AJDT Integration" -> press "Next >" -> Install -> Etc.
When the plugin is installed, and you've restarted Eclipse, the errors in your POM.xml
file should have gone away. If not, right-click your project to bring up the context menu: Maven -> Update Project -> press "OK".
Now for my actual test code class. Only one this time, TestClass.java
:
package my.package;
import my.package.TestDao;
import my.package.TestObject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.transaction.annotation.Transactional;
public void TestClass {
private TestDao testDao;
public void setTestDao(TestDao testDao) {
this.testDao = testDao;
}
public TestDao getTestDao() {
return testDao;
}
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");
TestClass testClass = applicationContext.getBean(TestClass.class);
testClass.nonTransactionalMethod();
}
public void nonTransactionalMethod() {
transactionalMethod();
}
@Transactional
private void transactionalMethod() {
TestObject testObject = new TestObject();
testObject.setId(1L);
testDao.save(testObject);
}
}
There is no trick to this one; since the weaving happens at compile time, which is before both the class loading and application context loading, the order of these two things no longer matters. This means that everything can go in the same class. In Eclipse, your code is constantly being re-compiled each time you hit Save (ever wondered what it was doing while it says "Building workspace: (XX%)"?), so it's woven and ready to go whenever you are.
Just like in the Load-Time example: the further particulars of TestObject
and TestDao
are unimportant. Assume they are wired up with JPA and Hibernate annotations and use Hibernate for persistence (because they are, and they do), and that all the requisite <bean />
's are set up in the application context file.
See Question&Answers more detail:
os