Sinossi della domanda originale: Utilizzo di transazioni Spring standard con proxy AOP, non è possibile chiamare un metodo @ Transactional contrassegnato da un valore non- @ Transactional- metodo contrassegnato nella stessa classe ed essere all'interno di una transazione (in particolare a causa del suddetto proxy). Questo è presumibilmente possibile con Transazioni di primavera in modalità AspectJ, ma come è fatto?Il vecchio "@Transactional all'interno della stessa classe" Situazione
Edit: La piena riduzione per le Operazioni di primavera in modalità AspectJ utilizzando load-time Tessitura:
Aggiungere il seguente alla META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
<context:load-time-weaver />
(darò per scontato che già hanno un AnnotationSessionFactoryBean
e un HibernateTransactionManager
impostati nel contesto dell'applicazione. È possibile aggiungere transaction-manager="transactionManager"
come attributo al tag <tx:annotation-driven />
, ma se il valore del bean del gestore transazioni è 0 attributoè in realtà "transactionManager
", allora è ridondante, come "transactionManager
" è il valore di default che dell'attributo.)
Aggiungi META-INF/aop.xml
. I contenuti sono i seguenti:
<aspectj>
<aspects>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
</aspects>
<weaver>
<include within="my.package..*" /><!--Whatever your package space is.-->
</weaver>
</aspectj>
Aggiungi aspectjweaver-1.7.0.jar
e spring-aspects-3.1.2.RELEASE.jar
al vostro classpath
. Io uso Maven come mio strumento di compilazione, per cui qui sono i <dependency />
dichiarazioni per POM.xml
il file del progetto:
<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
non è necessaria come <dependency />
sul classpath
, ma è ancora necessario che qualche parte in modo da poter punto con la bandiera -javaagent
JVM, come segue:
-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar
sto lavorando in Eclipse Juno, quindi per impostare questo sono andato a Window -> Preferenze -> Java -> JRE installati. Poi ho fatto clic sul JRE controllato nella casella di riepilogo e ho fatto clic sul pulsante "Modifica ..." a destra della casella di riepilogo. La terza casella di testo nella finestra popup risultante è etichettata come "Argomenti VM predefiniti:". Questo è dove la bandiera -javaagent
deve essere digitato o copia + incollato.
Ora, per le mie classi di codici di prova effettivi. In primo luogo, la mia classe principale, 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();
}
}
E poi la mia classe transazionale, 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);
}
}
Il trucco è che se il TestClass
è un campo in TestMain
sua classe verrà caricata dal ClassLoader
prima che venga caricato il contesto dell'applicazione. Poiché la tessitura è al momento del caricamento della classe e questa intrecciatura viene eseguita da Spring tramite il contesto dell'applicazione, non verrà tessuta perché la classe è già caricata prima che il contesto dell'applicazione venga caricato e ne venga a conoscenza.
Le ulteriori informazioni su TestObject
e TestDao
non sono importanti.Supponiamo che siano cablati con annotazioni JPA e Hibernate e usino Hibernate per la persistenza (perché lo sono, e lo fanno) e che tutti i requisiti <bean />
sono impostati nel file di contesto dell'applicazione.
Edit: La piena riduzione per le Operazioni di primavera in modalità AspectJ utilizzando Compile-Time Tessitura:
Aggiungere il seguente alla META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
(darò per scontato che già hanno un AnnotationSessionFactoryBean
e un HibernateTransactionManager
impostati nel contesto dell'applicazione. È possibile aggiungere transaction-manager="transactionManager"
come attributo al tag <tx:annotation-driven />
, ma se il valore del gestore delle transazioni attributo del bean id
è in realtà "transactionManager
", allora è ridondante, come "transactionManager
" è il valore di default che dell'attributo.)
Aggiungi spring-aspects-3.1.2.RELEASE.jar
e aspectjrt-1.7.0.jar
al vostro classpath
. Io uso Maven come mio strumento di compilazione, quindi ecco le <dependency />
dichiarazioni per il file 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>
In Eclipse Juno: Aiuto -> Eclipse Marketplace - casella di testo> etichettati "Trova:" -> tipo "ajdt" - > premi [Invio] -> "AspectJ Development Tools (Juno)" -> Installa -> Etc
Dopo aver riavviato Eclipse (ti farà), fai clic con il pulsante destro del mouse sul tuo progetto per visualizzare il menu di scelta rapida. Guarda in basso: Configura -> Converti in AspectJ Project.
Aggiungere il <plugin />
dichiarazione seguente nel POM.xml
(ancora una volta con la 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>
Alternativa: Fare clic con il progetto per far apparire il menu contestuale. Guarda in basso: AspectJ Tools -> Configura AspectJ Build Path -> scheda Aspect Path -> premi "Aggiungi JAR esterni ..." -> individua lo full/path/of/spring-aspects-3.1.2.RELEASE.jar
-> premi "Apri" -> premi "OK".
Se si è scelto il percorso Maven, lo <plugin />
sopra deve essere fuori di testa. Per risolvere questo problema: Guida -> Installa nuovo software ... -> premi "Aggiungi ..." -> digita quello che vuoi nella casella di testo "Nome:" -> digita o copia + incolla http://dist.springsource.org/release/AJDT/configurator/
nella casella di testo etichettata "Location:" -> premere "OK" -> Aspetta un secondo -> selezionare la casella di controllo genitore accanto a "Maven integrazione per Eclipse AJDT integrazione" -> premere "next>" -> Installa -> Ecc
Quando il plugin è installato e hai riavviato Eclipse, gli errori nel tuo file POM.xml
dovrebbero essere andati via. In caso contrario, fare clic con il tasto destro del mouse sul progetto per visualizzare il menu di scelta rapida: Maven -> Aggiorna progetto -> premere "OK".
Ora per la mia classe di codice di prova effettiva. Un solo questa volta, 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);
}
}
Non c'è trucco per questo; poiché la tessitura avviene al momento della compilazione, che è prima del caricamento della classe e del caricamento del contesto dell'applicazione, l'ordine di queste due cose non conta più. Ciò significa che tutto può andare nella stessa classe. In Eclipse, il codice viene costantemente ricompilato ogni volta che si preme Save (si è mai chiesto cosa stesse facendo mentre dice "Building workspace: (XX%)"?), Quindi è intessuto e pronto ad andare ogni volta che lo si fa.
Proprio come nell'esempio Load-Time: gli ulteriori dettagli di TestObject
e TestDao
non sono importanti. Supponiamo che siano cablati con annotazioni JPA e Hibernate e usino Hibernate per la persistenza (perché lo sono, e lo fanno) e che tutti i requisiti <bean />
sono impostati nel file di contesto dell'applicazione.
Giusto per essere sicuro - presumo che tu abbia definito un transactionManager, avvolgendo nella sessione di sospensioneFactory: org.springframework.orm.hibernate3.HibernateTransactionManager. Inoltre, puoi mostrare l'implementazione all'interno del tuo TestDao.Puoi confermare ulteriormente che quando chiami 'transactionMethod' direttamente da un test @Transactional, funziona correttamente e il problema si verifica solo quando chiami dal metodo' test'? –
Sarebbe un modo per iniettare l'oggetto in se stesso, e quindi chiamare il metodo sul riferimento iniettato, nella speranza che un proxy fosse stato iniettato? –