Сводка исходного вопроса: Используя стандартные транзакции Spring с прокси-сервером AOP, невозможно вызывать метод с маркировкой @Transactional из метода, отличного от @Transactional, в том же класса и быть в рамках транзакции (в частности, из-за вышеупомянутого прокси). Возможно, это возможно с помощью Spring Транзакций в режиме AspectJ, но как это сделать?
Изменить: Полное расписание для Spring Транзакций в режиме AspectJ с использованием Время загрузки Ткачество:
Добавьте в META-INF/spring/applicationContext.xml
следующее:
<tx:annotation-driven mode="aspectj" />
<context:load-time-weaver />
(Я предполагаю, что у вас уже есть AnnotationSessionFactoryBean
и HibernateTransactionManager
, настроенные в контексте приложения. Вы можете добавить transaction-manager="transactionManager"
в качестве атрибута к тегу <tx:annotation-driven />
, но если значение вашей транзакции Менеджером bean id
на самом деле является "transactionManager
", тогда он избыточен, так как "transactionManager
" - это значение по умолчанию этого атрибута.)
Добавить META-INF/aop.xml
. Содержание:
<aspectj>
<aspects>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
</aspects>
<weaver>
<include within="my.package..*" /><!--Whatever your package space is.-->
</weaver>
</aspectj>
Добавьте aspectjweaver-1.7.0.jar
и spring-aspects-3.1.2.RELEASE.jar
в свой classpath
. Я использую Maven в качестве моего инструмента построения, так что вот объявления <dependency />
для вашего проекта 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
не нужен как <dependency />
на вашем classpath
, но вы все равно нуждаетесь в нем где-нибудь, чтобы вы могли указать на него с флагом -javaagent
JVM следующим образом:
-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar
Я работаю в Eclipse Juno, поэтому, чтобы установить это, я перешел в Window → Preferences → Java → Установленные JRE. Затем я нажал на отмеченную JRE в списке и нажал кнопку "Изменить..." справа от поля списка. Третье текстовое поле в появившемся всплывающем окне помечено как "аргументы VM по умолчанию". Здесь должен быть напечатан флаг -javaagent
или скопировать + вставить.
Теперь для моих реальных классов тестового кода. Во-первых, мой основной класс 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();
}
}
И тогда мой транзакционный класс 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);
}
}
Трюк здесь в том, что если TestClass
является полем в TestMain
, его класс будет загружен ClassLoader
до того, как будет загружен контекст приложения. Так как плетение происходит во время загрузки класса, и это переплетение выполняется с помощью Spring через контекст приложения, оно не будет соткано, потому что класс уже загружен до того, как будет загружен контекст приложения и будет знать об этом.
Дальнейшие подробности TestObject
и TestDao
несущественны. Предположим, что они связаны с аннотациями JPA и Hibernate и используют Hibernate для сохранения (потому что они есть, и они это делают) и что все необходимые <bean />
настроены в файле контекста приложения.
Изменить: Полное расписание для транзакций Spring в режиме AspectJ с использованием Время компиляции Ткачество:
Добавьте в META-INF/spring/applicationContext.xml
следующее:
<tx:annotation-driven mode="aspectj" />
(Я предполагаю, что у вас уже есть AnnotationSessionFactoryBean
и HibernateTransactionManager
, настроенные в контексте приложения. Вы можете добавить transaction-manager="transactionManager"
в качестве атрибута к тегу <tx:annotation-driven />
, но если значение вашей транзакции Менеджером bean id
на самом деле является "transactionManager
", тогда он избыточен, так как "transactionManager
" - это значение по умолчанию этого атрибута.)
Добавьте spring-aspects-3.1.2.RELEASE.jar
и aspectjrt-1.7.0.jar
в classpath
. Я использую Maven как инструмент для сборки, поэтому здесь объявления <dependency />
для файла POM.xml
:
<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>
В Eclipse Juno: Справка → Eclipse Marketplace → текстовое поле с надписью "Найти:" → введите "ajdt" → нажмите [Enter] → "Средства разработки AspectJ (Juno)" → Install → Etc.
После перезапуска Eclipse (он сделает вас), щелкните правой кнопкой мыши проект, чтобы открыть контекстное меню. Посмотрите внизу: Configure → Convert to AspectJ Project.
Добавьте следующее <plugin />
объявление в POM.xml
(снова с 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>
Альтернатива: щелкните правой кнопкой мыши проект, чтобы открыть контекстное меню. Посмотрите в нижней части: AspectJ Tools → Настроить путь сборки AspectJ → вкладка "Аспектный путь" → нажмите "Добавить внешние JAR..." → найдите full/path/of/spring-aspects-3.1.2.RELEASE.jar
→ нажмите "Открыть" → нажмите "ОК" .
Если вы взяли маршрут Maven, <plugin />
выше должен быть freaking. Чтобы исправить это: Справка → Установить новое программное обеспечение... → нажмите "Добавить..." → введите то, что вам нравится, в текстовое поле с надписью "Имя:" → введите или скопируйте + вставить http://dist.springsource.org/release/AJDT/configurator/
в текст в поле "Место:" → нажмите "ОК" → Подождите секунду → установите флажок родительского флажка рядом с "Интеграция Maven для интеграции AJDT Eclipse" → нажмите "Далее > " → Установить → Эт.
Когда плагин установлен, и вы перезапустили Eclipse, ошибки в вашем файле POM.xml
должны были исчезнуть. Если нет, щелкните правой кнопкой мыши свой проект, чтобы открыть контекстное меню: Maven → Обновить проект → нажмите "ОК" .
Теперь для моего действительного класса тестового кода. Только на этот раз 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);
}
}
В этом нет никакого трюка; поскольку во время компиляции происходит переплетение, которое до загрузки как загрузки классов, так и загрузки приложения, порядок этих двух вещей больше не имеет значения. Это означает, что все может идти в одном классе. В Eclipse ваш код постоянно перекомпилируется каждый раз, когда вы нажимаете Save (когда-либо задавались вопросом, что он делает, в то время как он говорит "Building workspace: (XX%)"?), Поэтому он соткан и готов к работе, когда вы находитесь.
Как и в примере Load-Time: дальнейшие подробности TestObject
и TestDao
несущественны. Предположим, что они связаны с аннотациями JPA и Hibernate и используют Hibernate для сохранения (потому что они есть, и они это делают) и что все необходимые <bean />
настроены в файле контекста приложения.