O velho “@Transacional de dentro da mesma classe” Situação

Sinopse da pergunta original: Usando Spring Transactions padrão com proxy AOP, não é possível chamar um método marcado @ Transactional de um método não marcado @ Transactional na mesma classe e estar dentro de uma transação (especificamente devido ao proxy mencionado anteriormente). Isso é supostamente possível com o Spring Transactions no modo AspectJ, mas como é feito?

Editar: O resumo completo para Spring Transactions no modo AspectJ usandoTempo de carregamento Tecelagem:

Adicione o seguinte paraMETA-INF/spring/applicationContext.xml:

<tx:annotation-driven mode="aspectj" />

<context:load-time-weaver />

(Eu suponho que você já tem umAnnotationSessionFactoryBean&nbsp;e umHibernateTransactionManager&nbsp;configurado no contexto da aplicação. Você pode adicionartransaction-manager="transactionManager"&nbsp;como um atributo para o seu<tx:annotation-driven />&nbsp;tag, mas se o valor do bean do seu gerenciador de transaçõesid&nbsp;atributo é realmente "transactionManager", então é redundante, como"transactionManager"é o valor padrão desse atributo.)

AdicionarMETA-INF/aop.xml. Os conteúdos são os seguintes:

<aspectj>
  <aspects>
    <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
  </aspects>
  <weaver>
    <include within="my.package..*" /><!--Whatever your package space is.-->
  </weaver>
</aspectj>

Adicionaraspectjweaver-1.7.0.jar&nbsp;espring-aspects-3.1.2.RELEASE.jar&nbsp;para o seuclasspath. Eu uso o Maven como minha ferramenta de compilação, então aqui estão as<dependency />&nbsp;declarações para o seu projetoPOM.xml&nbsp;Arquivo:

<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&nbsp;não é necessário como um<dependency />&nbsp;Nas suasclasspath, mas você ainda precisaalgum lugar&nbsp;de modo que você pode apontar para ele com o-javaagent&nbsp;Sinalizador de JVM, da seguinte maneira:

-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar

Eu estou trabalhando no Eclipse Juno, então para definir isso, eu fui para Window -> Preferences -> Java -> JREs instalados. Então cliquei no JRE marcado na caixa de listagem e cliquei no botão "Editar ..." à direita da caixa de listagem. A terceira caixa de texto na janela pop-up resultante é rotulada "Argumentos de VM padrão:". É aqui que o-javaagent&nbsp;flag deve ser digitado ou copiado + colado em.

Agora para minhas classes de código de teste reais. Primeiro, minha turma principalTestMain.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 então minha classe transacional,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);
  }
}

O truque aqui é que, se oTestClass&nbsp;é um campo emTestMain&nbsp;sua classe será carregada peloClassLoader&nbsp;antes que o contexto do aplicativo seja carregado. Como a tecelagem está no tempo de carregamento da classe, e essa tecelagem é feita pela Spring através do contexto da aplicação, ela não será entrelaçada porque a classe já está carregada antes que o contexto da aplicação seja carregado e esteja ciente disso.

As outras particularidades doTestObject&nbsp;eTestDao&nbsp;são sem importância. Suponha que eles estejam conectados com anotações JPA e Hibernate e usem o Hibernate para persistência (porque eles são e funcionam), e que todos os requisitos<bean />são configurados no arquivo de contexto do aplicativo.

Editar:&nbsp;O resumo completo para Spring Transactions no modo AspectJ usandoCompile-Time&nbsp;Tecelagem:

Adicione o seguinte paraMETA-INF/spring/applicationContext.xml:

<tx:annotation-driven mode="aspectj" />

(Eu suponho que você já tem umAnnotationSessionFactoryBean&nbsp;e umHibernateTransactionManager&nbsp;configurado no contexto da aplicação. Você pode adicionartransaction-manager="transactionManager"&nbsp;como um atributo para o seu<tx:annotation-driven />&nbsp;tag, mas se o valor do bean do seu gerenciador de transaçõesid&nbsp;atributo é realmente "transactionManager", então é redundante, como"transactionManager"é o valor padrão desse atributo.)

Adicionarspring-aspects-3.1.2.RELEASE.jar&nbsp;easpectjrt-1.7.0.jar&nbsp;para o seuclasspath. Eu uso o Maven como minha ferramenta de construção, então aqui está o<dependency />&nbsp;declarações para oPOM.xml&nbsp;Arquivo:

<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>

No Eclipse Juno: Ajuda -> Eclipse Marketplace -> caixa de texto rotulado "Find:" -> digite "ajdt" -> hit [Enter] -> "AspectJ Development Tools (Juno)" -> Instalar -> Etc.

Depois de reiniciar o Eclipse (ele fará você), clique com o botão direito do mouse em seu projeto para abrir o menu de contexto. Olhe na parte inferior: Configure -> Convert to AspectJ Project.

Adicione o seguinte<plugin />&nbsp;declaração em seuPOM.xml&nbsp;(novamente com o 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: Clique com o botão direito do mouse no seu projeto para abrir o menu de contexto. Olhe na parte inferior: AspectJ Tools -> Configure AspectJ Build Path -> guia Aspect Path -> pressione "Add External JARs ..." -> localize o caminhofull/path/of/spring-aspects-3.1.2.RELEASE.jar&nbsp;-> pressione "Abrir" -> pressione "OK".

Se você tomou a rota Maven, o<plugin />&nbsp;acima deve estar em pânico. Para corrigir isso: Ajuda -> Instalar Novo Software ... -> pressione "Adicionar ..." -> digite o que quiser na caixa de texto chamada "Nome:" -> digite ou copie + colehttp://dist.springsource.org/release/AJDT/configurator/&nbsp;na caixa de texto chamada "Local:" -> pressione "OK" -> Aguarde um segundo -> marque a caixa de seleção pai ao lado de "Integração Maven para integração do Eclipse AJDT" -> pressione "Avançar>" -> Instalar -> Etc.

Quando o plug-in é instalado e você reiniciou o Eclipse, os erros em seuPOM.xml&nbsp;arquivo deveria ter ido embora. Se não, clique com o botão direito do mouse em seu projeto para abrir o menu de contexto: Maven -> Update Project -> pressione "OK".

Agora, para minha aula de código de teste real. Apenas uma desta vezTestClass.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);
  }
}

Não há truque para este; como a tecelagem acontece em tempo de compilação, o que ocorre antes do carregamento da classe e do carregamento do contexto do aplicativo, a ordem dessas duas coisas não importa mais. Isso significa que tudo pode ir na mesma classe. No Eclipse, seu código está sendo constantemente compilado cada vez que você clica em Save (já imaginou o que estava fazendo enquanto diz "Building workspace: (XX%)"?), Então ele está pronto e pronto para ir quando você estiver.

Assim como no exemplo do Load-Time: os detalhes adicionais deTestObject&nbsp;eTestDao&nbsp;são sem importância. Suponha que eles estejam conectados com anotações JPA e Hibernate e usem o Hibernate para persistência (porque eles são e funcionam), e que todos os requisitos<bean />são configurados no arquivo de contexto do aplicativo.