Старая ситуация «@Transactional из одного класса»
Synopsis of the original question: Используя стандартные транзакции Spring с прокси-сервером AOP, невозможно вызвать метод @ Transactional-отмеченный из не-@ Transactional-отмеченного метода в том же классе и находиться внутри транзакции (в частности, из-за вышеупомянутого прокси). Предположительно, это возможно с Spring Transactions в режиме AspectJ, но как это сделать?
Edit: Полное краткое изложение для Spring транзакций в режиме AspectJ с использованиемLoad-Time Плетение:
Добавьте следующее кMETA-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
<context:load-time-weaver />
(Я предполагаю, что у вас уже естьAnnotationSessionFactoryBean
иHibernateTransactionManager
настроить в контексте приложения. Можете добавитьtransaction-manager="transactionManager"
как атрибут вашего<tx:annotation-driven />
тег, но если значение вашего компонента управления транзакциямиid
атрибут на самом деле & quot;transactionManager
", тогда это избыточно, как"transactionManager
& Quot; значение этого атрибута по умолчанию.)
добавлять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
файл:
<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
, но это все еще нужноsomewhere так что вы можете указать на это с-javaagent
Флаг JVM, следующим образом:
-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar
Я работаю в Eclipse Juno, поэтому чтобы установить это, я выбрал Window - & gt; Настройки - & gt; Java - & gt; Установленные JRE. Затем я щелкнул отмеченную JRE в списке и нажал кнопку & quot; Изменить ... & quot; Кнопка справа от списка. Третье текстовое поле в появившемся всплывающем окне помечено как «Аргументы виртуальной машины по умолчанию:». Это где-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 />
в файле контекста приложения.
Edit: Полное краткое изложение для Spring транзакций в режиме AspectJ с использованиемCompile-Time Плетение:
Добавьте следующее кMETA-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
(Я предполагаю, что у вас уже естьAnnotationSessionFactoryBean
иHibernateTransactionManager
настроить в контексте приложения. Можете добавитьtransaction-manager="transactionManager"
как атрибут вашего<tx:annotation-driven />
тег, но если значение вашего компонента управления транзакциямиid
атрибут на самом деле & quot;transactionManager
", тогда это избыточно, как"transactionManager
& Quot; значение этого атрибута по умолчанию.)
добавлять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: Справка - & gt; Торговая площадка Eclipse - & gt; текстовое поле с надписью & quot; Поиск: & quot; - & GT; введите & quot; ajdt & quot; - & GT; нажмите [Enter] - & gt; & quot; Инструменты разработки AspectJ (Juno) & quot; - & GT; Установить - & gt; И т.п.
После перезапуска Eclipse (это сделает вас) щелкните правой кнопкой мыши ваш проект, чтобы вызвать контекстное меню. Посмотрите внизу: Настроить - & gt; Конвертировать в AspectJ Project.
Добавьте следующее<plugin />
декларация в вашемPOM.xml
(опять с мавеном!)
<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 - & gt; Настроить AspectJ Build Path - & gt; Вкладка Aspect Path - & gt; нажмите & quot; Добавить внешние файлы JAR ... & quot; - & GT; найдитеfull/path/of/spring-aspects-3.1.2.RELEASE.jar
- & GT; нажмите & quot; Открыть & quot; - & GT; нажмите "ОК".
Если вы взяли маршрут Maven, то<plugin />
выше должно быть псих. Чтобы это исправить: Справка - & gt; Установить новое программное обеспечение ... - & gt; нажмите & quot; Добавить ... & quot; - & GT; введите все, что вам нравится, в текстовое поле с надписью & quot; Имя: & quot; - & GT; введите или скопируйте + вставьтеhttp://dist.springsource.org/release/AJDT/configurator/
в текстовом поле с надписью & quot; Местоположение: & quot; - & GT; нажмите "ОК" - & GT; Подождите секунду - & gt; установите родительский флажок рядом с «Интеграция Maven для Eclipse AJDT Integration». - & GT; нажмите & quot; Далее & gt; & quot; - & GT; Установить - & gt; И т.п.
Когда плагин установлен, и вы перезапустили Eclipse, ошибки в вашемPOM.xml
файл должен был уйти. Если нет, щелкните правой кнопкой мыши свой проект, чтобы открыть контекстное меню: Maven - & gt; Обновить проект - & gt; нажмите "ОК".
Теперь для моего реального класса тестового кода. Только один раз,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 ваш код постоянно перекомпилируется каждый раз, когда вы нажимаете кнопку Сохранить (когда-нибудь задумывались над тем, что он делал, когда он говорит «Создание рабочего пространства: (XX%)»), так что он сплетен и готов к работе всякий раз, когда ты.
Как в примере с Load-Time: дальнейшие подробностиTestObject
а такжеTestDao
неважны. Предположим, что они связаны с аннотациями JPA и Hibernate и используют Hibernate для постоянства (потому что они есть, и они делают), и что все необходимое<bean />
в файле контекста приложения.