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();
Juha Syrjälä
fonte

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

Ralph
fonte
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.

skaffman
fonte
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.

Juha Syrjälä
fonte
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.

James Watkins
fonte
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(){
        //...
    }

}
loonis
fonte
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

user536161
fonte
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
3

A resposta é não. Consulte a Referência da Primavera: Usando @Transactional :

A @Transactionalanotação pode ser colocada antes de uma definição de interface, um método em uma interface, uma definição de classe ou um método público em uma classe

hera
fonte
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.

Lu55
fonte