Sinopsis de la pregunta original: Usando transacciones estándar de primavera con proxy AOP, no es posible llamar a @ Transaccional-marcado método de un @ Transactional- método marcado en la misma clase y dentro de una transacción (específicamente debido al proxy mencionado anteriormente). Esto es supuestamente posible con Spring Transactions en modo AspectJ, pero ¿cómo se hace?El viejo "@Transactional desde dentro de la misma clase" Situación
Editar: El resumen completo de las transacciones de primavera en el modo AspectJ utilizando de carga en tiempo tejer:
Añadir lo siguiente a META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
<context:load-time-weaver />
(voy a suponer que ya tienen un AnnotationSessionFactoryBean
y una HibernateTransactionManager
establecido en el contexto de aplicación. Se pueden añadir transaction-manager="transactionManager"
como un atributo de la etiqueta <tx:annotation-driven />
, pero si el valor de su gestor de beans de transacción id
atributo es en realidad "transactionManager
", entonces es redundante, ya que "transactionManager
" es el valor por defecto de ese atributo.)
Añadir META-INF/aop.xml
. Contenido es el siguiente:
<aspectj>
<aspects>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
</aspects>
<weaver>
<include within="my.package..*" /><!--Whatever your package space is.-->
</weaver>
</aspectj>
Añadir aspectjweaver-1.7.0.jar
y spring-aspects-3.1.2.RELEASE.jar
a su classpath
. Yo uso Maven como mi herramienta de construcción, así que aquí están los <dependency />
declaraciones para POM.xml
de archivos de su proyecto:
<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
no es necesaria como <dependency />
en su classpath
, pero todavía lo necesito algún lugar, para que pueda punto en el que con la bandera -javaagent
JVM, de la siguiente manera:
-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar
estoy trabajando en Eclipse Juno, por lo que establece este fui a la ventana -> Preferencias -> Java -> JRE instalados. Luego hice clic en el JRE marcado en el cuadro de lista y presioné el botón "Editar ..." a la derecha del cuadro de lista. El tercer cuadro de texto en la ventana emergente resultante está etiquetado como "Argumentos VM por defecto". Aquí es donde la bandera -javaagent
se debe escribir o copiar + pegar.
Ahora para mis clases de código de prueba reales. En primer lugar, mi clase principal, 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();
}
}
Y entonces mi clase de transacciones, 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);
}
}
El truco aquí es que si el TestClass
es un campo en TestMain
su clase será cargado por el ClassLoader
antes de que se cargue el contexto de la aplicación. Como el entrelazado está en el tiempo de carga de la clase, y este entrelazado lo realiza Spring en el contexto de la aplicación, no se teje porque la clase ya está cargada antes de que el contexto de la aplicación se cargue y esté al tanto.
Los detalles adicionales de TestObject
y TestDao
no son importantes.Supongamos que están conectados con anotaciones JPA e Hibernate y usan Hibernate para la persistencia (porque lo son, y lo hacen), y que todos los requisitos <bean />
están configurados en el archivo de contexto de la aplicación.
Editar: El resumen completo de las transacciones de primavera en el modo AspectJ utilizando de tiempo de compilación tejer:
Añadir lo siguiente a META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
(voy a suponer que ya tienen AnnotationSessionFactoryBean
y HibernateTransactionManager
configurados en el contexto de la aplicación. Puede agregar transaction-manager="transactionManager"
como un atributo a su etiqueta <tx:annotation-driven />
, pero si el valor de su administrador de transacciones atributo de frijol id
es en realidad "transactionManager
", entonces es redundante, ya que "transactionManager
" es el valor por defecto de ese atributo.)
Añadir spring-aspects-3.1.2.RELEASE.jar
y aspectjrt-1.7.0.jar
a su classpath
. Yo uso Maven como mi herramienta de construcción, así que aquí tiene las <dependency />
declaraciones para el archivo 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>
En Eclipse Juno: Ayuda -> Eclipse del mercado -> cuadro de texto llamado "Encontrar:" -> tipo "ajdt" - > pulse [Entrar] -> "Herramientas de desarrollo AspectJ (Juno)" -> Instalar -> Etc.
Después de reiniciar Eclipse (lo hará), haga clic derecho en su proyecto para que aparezca el menú contextual. Mire cerca de la parte inferior: Configurar -> Convertir a Proyecto AspectJ.
Añadir la <plugin />
declaración siguiente en su POM.xml
(de nuevo con el 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: Haga clic derecho en su proyecto para que aparezca el menú contextual. Mire cerca de la parte inferior: AspectJ Herramientas -> Configure AspectJ Build Path -> Aspect Path tab -> presione "Add External JARs ..." -> locate the full/path/of/spring-aspects-3.1.2.RELEASE.jar
-> presione "Abrir" -> presione "OK".
Si tomó la ruta Maven, el <plugin />
anterior debería estar volviendo loco. Para solucionarlo: Ayuda -> Instalar nuevo software ... -> presione "Agregar ..." -> escriba lo que quiera en el cuadro de texto etiquetado "Nombre:" -> escriba o copie + pegue http://dist.springsource.org/release/AJDT/configurator/
en el cuadro de texto etiquetado "Ubicación:" -> presione "OK" -> Espere un segundo -> marque la casilla de verificación principal al lado de "Integración de Maven para Integración AJDT de Eclipse" -> presione "Siguiente>" -> Instalar -> Etc.
Cuando el complemento está instalado y reinició Eclipse, los errores en su archivo POM.xml
deberían haber desaparecido. De lo contrario, haga clic con el botón derecho en su proyecto para que aparezca el menú contextual: Maven -> Actualizar proyecto -> presione "Aceptar".
Ahora para mi clase de código de prueba real. Sólo uno de este tiempo, 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);
}
}
No hay truco a éste; dado que el entrelazado ocurre en el momento de la compilación, que es anterior a la carga de clases y a la carga del contexto de la aplicación, el orden de estas dos cosas ya no importa. Esto significa que todo puede ir en la misma clase. En Eclipse, su código se vuelve a compilar constantemente cada vez que presiona Guardar (¿alguna vez se preguntó qué estaba haciendo mientras decía "Construir espacio de trabajo: (XX%)"?), Por lo que está entretejido y listo para donde quiera que esté.
Al igual que en el ejemplo de la carga-tiempo: los detalles adicionales de TestObject
y TestDao
no son importantes. Supongamos que están conectados con anotaciones JPA e Hibernate y usan Hibernate para la persistencia (porque lo son, y lo hacen), y que todos los requisitos <bean />
están configurados en el archivo de contexto de la aplicación.
Solo para asegurarte: supongo que tienes un transactionManager definido, envolviendo en hibernate sessionFactory: org.springframework.orm.hibernate3.HibernateTransactionManager. Además, ¿puedes mostrar la implementación dentro de tu TestDao?¿Puedes confirmar que cuando llamas 'transactionMethod' directamente desde dentro de una prueba @Transactional, ¿funciona correctamente y el problema es solo cuando llamas desde el método' test'? –
¿Sería una forma de evitar esto inyectar el objeto en sí mismo, y luego llamar al método en la referencia inyectada, con la esperanza de que se haya inyectado un proxy? –