O atributo Spring @Transactional funciona em um método privado?


196

Se eu tiver uma anotação @Transactional em um método privado em um bean Spring, a anotação tem algum efeito?

Se a @Transactionalanotação estiver em um método público, ela funcionará e abrirá uma transação.

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Respostas:


163

A questão não é privada ou pública, a questão é: como é invocada e qual implementação de AOP você usa!

Se você usar o Spring Proxy AOP (padrão), todas as funcionalidades do AOP fornecidas pelo Spring (como @Transational) serão consideradas apenas se a chamada passar pelo proxy. - Normalmente, este é o caso se o método anotado for chamado de outro bean.

Isso tem duas implicações:

  • Como os métodos privados não devem ser invocados a partir de outro bean (a exceção é reflexão), suas @TransactionalAnotações não são levadas em consideração.
  • Se o método for público, mas for invocado a partir do mesmo bean, ele também não será levado em consideração (esta instrução estará correta apenas se for usado o Spring Proxy AOP (padrão)).

Consulte a Referência do Spring: Capítulo 9.6 9.6 Mecanismos de proxy

IMHO, você deve usar o modo aspectJ, em vez dos Spring Proxies, que resolverão o problema. E os Aspectos Transacionais AspectJ são tecidos até em métodos privados (verificados no Spring 3.0).


4
Ambos os pontos não são necessariamente verdadeiros. O primeiro está incorreto - os métodos privados podem ser chamados reflexivamente, mas a lógica de descoberta de proxy decide não fazer isso. O segundo ponto é verdadeiro apenas para proxies JDK baseados em interface, mas não para proxies baseados em subclasse CGLIB.
— 21310 skaffman

@skaffman: 1 - eu faço minha declaração mais precisa, 2. Mas o Proxy padrão é baseado na interface - não é?
— Ralph

2
Isso depende se o destino usa interfaces ou não. Caso contrário, o CGLIB é usado.
— skaffman

canu me diga o reson ou alguma referência por que cglib não pode mas aspectj pode?
— Phil

1
Referência a partir do link no bloco de respostas, se você quiser usar o Spring Proxies [ambiente padrão], coloque a anotação em doStuff () e chame doPrivateStuff () usando ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Ele executará os dois métodos em uma mesma transação se a propagação for reutilizada [ambiente padrão].
— Michael Ouyang

219

A resposta da sua pergunta é não - não @Transactionalterá efeito se usada para anotar métodos privados. O gerador de proxy os ignorará.

Isso está documentado no capítulo 10.5.6 do Manual da Primavera :

Visibilidade do método e @Transactional

Ao usar proxies, você deve aplicar a @Transactionalanotação apenas a métodos com visibilidade pública. Se você anotar métodos protegidos, privados ou visíveis no pacote com a @Transactionalanotação, nenhum erro será gerado, mas o método anotado não exibirá as configurações transacionais definidas. Considere o uso do AspectJ (veja abaixo) se você precisar anotar métodos não públicos.


Você tem certeza disso? Eu não esperaria que isso fizesse diferença.
— Willcodejavaforfood

e se o estilo de proxy for Cglib?
— Lily

32

Por padrão, o @Transactionalatributo funciona apenas ao chamar um método anotado em uma referência obtida do applicationContext.

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

Isso abrirá uma transação:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

Isto não irá:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Referência da Primavera: Usando @Transactional

Nota: No modo proxy (que é o padrão), apenas as chamadas de método 'externas' que chegam pelo proxy serão interceptadas. Isso significa que 'auto-invocação', ou seja, um método dentro do objeto de destino que chama outro método do objeto de destino, não levará a uma transação real em tempo de execução, mesmo que o método invocado esteja marcado com@Transactional !

Considere o uso do modo AspectJ (veja abaixo) se você espera que as auto-invocações sejam envolvidas com transações também. Nesse caso, não haverá um proxy em primeiro lugar; em vez disso, a classe de destino será 'tecida' (ou seja, seu código de bytes será modificado) para se transformar @Transactionalem comportamento de tempo de execução em qualquer tipo de método.


Você quer dizer bean = new Bean () ;?
— Willcodejavaforfood

Não. Se eu criar beans com o novo Bean (), a anotação nunca funcionará pelo menos sem usar o Aspect-J.
— Juha Syrjälä

2
obrigado! Isso explica o comportamento estranho que eu estava observando. Bastante contra-intuitivo esta restrição método de invocação interna ...
— manuel aldana

Eu aprendi as "chamadas de método única externos que entram através do proxy serão interceptadas" da maneira mais difícil
— ASGs

13

Sim, é possível usar o @Transactional em métodos privados, mas, como outros usuários mencionaram, isso não funcionará imediatamente. Você precisa usar o AspectJ. Levei algum tempo para descobrir como fazê-lo funcionar. Vou compartilhar meus resultados.

Eu escolhi usar a tecelagem em tempo de compilação em vez da tecelagem em tempo de carga porque acho que é uma opção melhor em geral. Além disso, estou usando o Java 8, portanto, pode ser necessário ajustar alguns parâmetros.

Primeiro, adicione a dependência para aspectjrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Em seguida, adicione o plug-in AspectJ para fazer a tecelagem de bytecode real no Maven (este pode não ser um exemplo mínimo).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Por fim, adicione isso à sua classe de configuração

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Agora você deve poder usar o @Transactional em métodos privados.

Uma ressalva a esta abordagem: você precisará configurar seu IDE para conhecer o AspectJ, caso contrário, se você executar o aplicativo via Eclipse, por exemplo, ele poderá não funcionar. Teste contra uma compilação direta do Maven como uma verificação de sanidade.


se o método de proxy for cglib, não há necessidade de implementar uma interface da qual o método deve ser público; ele poderá usar o @Transactional em métodos privados?
— Lily

Sim, funciona em métodos privados e sem interfaces! Desde que o AspectJ esteja configurado corretamente, basicamente garante os decoradores dos métodos de trabalho. E user536161 apontou em sua resposta que funcionará mesmo em auto-invocações. É muito legal e um pouco assustador.
— James Watkins

12

Se você precisar agrupar um método privado dentro de uma transação e não quiser usar o aspectj, poderá usar o TransactionTemplate .

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}

Bom para mostrar TransactionTemplate uso, mas chame esse segundo método em ..RequiresTransactionvez de ..InTransaction. Sempre nomeie as coisas como gostaria de lê-las um ano depois. Também argumentaria para pensar se ele realmente requer um segundo método privado: coloque seu conteúdo diretamente na executeimplementação anônima ou se isso ficar confuso, pode ser uma indicação para dividir a implementação em outro serviço que você pode anotar @Transactional.
— Preso

@ Preso, o segundo método não é realmente necessário, mas responde à pergunta original, que é como aplicar uma transação de primavera em um método privado
— loonis

Sim, eu já votei positivamente na resposta, mas queria compartilhar algum contexto e pensamentos sobre como aplicá-la, porque, do ponto de vista da arquitetura, essa situação é uma indicação potencial para uma falha de design.
— Preso

5

Os documentos do Spring explicam que

No modo proxy (que é o padrão), apenas as chamadas de método externas que chegam pelo proxy são interceptadas. Isso significa que a auto-invocação, com efeito, um método dentro do objeto de destino que chama outro método do objeto de destino, não levará a uma transação real no tempo de execução, mesmo se o método chamado estiver marcado com @Transactional.

Considere o uso do modo AspectJ (consulte o atributo mode na tabela abaixo) se você espera que as auto-invocações sejam agrupadas também com transações. Nesse caso, não haverá um proxy em primeiro lugar; em vez disso, a classe de destino será tecida (ou seja, seu código de bytes será modificado) para transformar @Transactional em comportamento de tempo de execução em qualquer tipo de método.

Outra maneira é o usuário BeanSelfAware


você poderia adicionar uma referência BeanSelfAware? Ele não se parece com a classe de um Primavera
— ASGs

@asgs Suponha que se trata de auto-injeção (forneça um bean com uma instância dele envolvida em um proxy). Você pode ver exemplos em stackoverflow.com/q/3423972/355438 .
— Lu55 5/05/19


1

Da mesma maneira que @loonis sugeriu usar o TransactionTemplate, um pode usar este componente auxiliar (Kotlin):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

Uso:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

Não sei se TransactionTemplatereutilizará a transação existente ou não, mas esse código definitivamente o faz.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.