Spring - @Transactional - O que acontece em segundo plano?

334

Quero saber o que realmente acontece quando você anota um método com @Transactional? Obviamente, eu sei que o Spring envolverá esse método em uma transação.

Mas tenho as seguintes dúvidas:

  1. Ouvi dizer que o Spring cria uma classe proxy ? Alguém pode explicar isso com mais profundidade . O que realmente reside nessa classe de proxy? O que acontece com a classe real? E como posso ver a classe proxy criada pelo Spring
  2. Também li nos documentos do Spring que:

Nota: Como esse mecanismo é baseado em proxies, apenas as chamadas de método 'externas' que chegam pelo proxy serão interceptadas . Isso significa que a '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!

Fonte: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Por que apenas chamadas de método externo estarão em Transação e não os métodos de auto-invocação?

pico
fonte
2
Discussão relevante está aqui: stackoverflow.com/questions/3120143/…
dma_k

Respostas:

255

Este é um grande tópico. O documento de referência do Spring dedica vários capítulos a ele. Eu recomendo a leitura sobre Programação e transações orientadas a aspectos , pois o suporte a transações declarativas do Spring usa o AOP em sua base.

Mas em um nível muito alto, o Spring cria proxies para classes que declaram @Transactional na própria classe ou em membros. O proxy é quase invisível no tempo de execução. Ele fornece uma maneira de o Spring injetar comportamentos antes, depois ou em torno das chamadas de método para o objeto que está sendo proxy. O gerenciamento de transações é apenas um exemplo dos comportamentos que podem ser conectados. As verificações de segurança são outro. E você também pode fornecer o seu próprio para coisas como registro em log. Então, quando você anota um método com @Transactional , o Spring cria dinamicamente um proxy que implementa as mesmas interfaces que a classe que você está anotando. E quando os clientes fazem chamadas para o seu objeto, as chamadas são interceptadas e os comportamentos injetados pelo mecanismo de proxy.

As transações no EJB funcionam da mesma forma, a propósito.

Como você observou, o mecanismo de proxy funciona apenas quando as chamadas são recebidas de algum objeto externo. Quando você faz uma chamada interna dentro do objeto, está realmente fazendo uma chamada através da referência " this ", que ignora o proxy. Existem maneiras de contornar esse problema, no entanto. Eu explico uma abordagem nesta postagem do fórum em que eu uso um BeanFactoryPostProcessor para injetar uma instância do proxy em classes de "auto-referência" em tempo de execução. Salvei essa referência em uma variável de membro chamada " eu ". Então, se eu precisar fazer chamadas internas que exijam uma alteração no status da transação do encadeamento, direciono a chamada através do proxy (por exemplo, " me.someMethod ()".) A postagem do fórum explica com mais detalhes. Observe que o código BeanFactoryPostProcessor seria um pouco diferente agora, como foi escrito no período de tempo do Spring 1.x. Mas espero que tenha uma idéia. Tenho uma versão atualizada que Eu provavelmente poderia disponibilizar.

Rob H
fonte
4
>> O proxy é praticamente invisível no tempo de execução Oh !! Estou curioso para vê-los :) Descanse .. sua resposta foi muito abrangente. Esta é a segunda vez que você está me ajudando .. Obrigado por toda a ajuda.
peakit
17
Sem problemas. Você pode ver o código do proxy se avançar com um depurador. Essa é provavelmente a maneira mais fácil. Não há mágica; são apenas classes dentro dos pacotes Spring.
Rob H
E se o método que possui a anotação @Transaction estiver implementando uma interface, o spring usará a API do proxy dinâmico para injetar a transação e não usar proxies. Prefiro que minhas classes transacionalizadas implementem interfaces em qualquer caso.
22611 Michael Wiles
1
Também encontrei o esquema “eu” (usando a fiação explícita para fazê-lo da maneira que penso), mas acho que se você estiver fazendo dessa maneira, provavelmente será melhor refatorar para não tem que. Mas sim, isso às vezes pode ser muito estranho!
Donal Fellows
2
2019: como essa resposta está envelhecendo, a postagem do fórum referida não está mais disponível, o que descreveria o caso em que você deve fazer uma chamada interna no objeto sem ignorar o proxy usandoBeanFactoryPostProcessor . No entanto, existe um método (na minha opinião) muito semelhante descrito nesta resposta: stackoverflow.com/a/11277899/3667003 ... e outras soluções em todo o segmento também.
Z3d4s 03/04/19
196

Quando o Spring carrega suas definições de bean e é configurado para procurar @Transactionalanotações, ele cria esses objetos proxy em torno de seu bean real . Esses objetos de proxy são instâncias de classes geradas automaticamente em tempo de execução. O comportamento padrão desses objetos proxy quando um método é chamado é apenas para invocar o mesmo método no bean "target" (ou seja, seu bean).

No entanto, os proxies também podem ser fornecidos com interceptores e, quando presentes, esses interceptores serão chamados pelo proxy antes de invocar o método do seu bean de destino. Para os beans de destino anotados com @Transactional, o Spring criará um TransactionInterceptore o passará para o objeto proxy gerado. Portanto, quando você chama o método a partir do código do cliente, está chamando o método no objeto proxy, que primeiro chama o TransactionInterceptor(que inicia uma transação), que por sua vez chama o método no seu bean de destino. Quando a chamada termina, a TransactionInterceptorconsolidação / reverte a transação. É transparente para o código do cliente.

Quanto à coisa "método externo", se o seu bean chamar um de seus próprios métodos, ele não o fará através do proxy. Lembre-se, o Spring envolve seu bean no proxy, seu bean não o conhece. Somente chamadas de "fora" do seu bean passam pelo proxy.

Isso ajuda?

skaffman
fonte
36
> Lembre-se, o Spring envolve seu bean no proxy, seu bean não tem conhecimento disso. Isso já dizia tudo. Que ótima resposta. Obrigado por ajudar.
peakit
Ótima explicação, para o proxy e os interceptadores. Agora eu entendo o Spring implementar um objeto proxy para interceptar chamadas para um bean de destino. Obrigado!
dharag
Eu acho que você está tentando descrever esta imagem da documentação do Spring e ver essa imagem me ajuda muito: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun
44

Como pessoa visual, eu gosto de pesar com um diagrama de seqüência do padrão de proxy. Se você não sabe ler as setas, eu li a primeira assim: Clientexecuta Proxy.method().

  1. O cliente chama um método no alvo de sua perspectiva e é silenciosamente interceptado pelo proxy
  2. Se um aspecto anterior for definido, o proxy o executará
  3. Em seguida, o método real (destino) é executado
  4. Após retorno e pós-lançamento são aspectos opcionais que são executados após o retorno do método e / ou se o método lança uma exceção
  5. Depois disso, o proxy executa o aspecto posterior (se definido)
  6. Finalmente, o proxy retorna ao cliente que está chamando

Diagrama de sequência de padrões de proxy (Pude postar a foto com a condição de mencionar suas origens. Autor: Noel Vaes, site: www.noelvaes.eu)

progonkpa
fonte
27

A resposta mais simples é:

Em qualquer método, você declara que @Transactionalo limite da transação é iniciado e o limite termina quando o método é concluído.

Se você estiver usando a chamada JPA, todas as confirmações estarão nesse limite de transação .

Digamos que você esteja salvando entidade1, entidade2 e entidade3. Agora, ao salvar a entidade3 , ocorre uma exceção ; então, como enitiy1 e entity2 entram na mesma transação, a entidade1 e a entidade2 serão revertidas com a entidade3.

Transação:

  1. entity1.save
  2. entity2.save
  3. entity3.save

Qualquer exceção resultará na reversão de todas as transações JPA com DB. As transações JPA internamente são usadas pelo Spring.

RoshanKumar Mutha
fonte
2
"Uma exceção resultará na reversão de todas as transações JPA com DB." Nota Apenas RuntimeException resulta em reversão. As exceções marcadas não resultam em reversão.
Arjun #
2

Pode ser tarde, mas me deparei com algo que explica sua preocupação com o proxy (apenas chamadas de método 'externas' que chegam pelo proxy serão interceptadas) muito bem.

Por exemplo, você tem uma classe que se parece com isso

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

e você tem um aspecto que se parece com isso:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Quando você o executa assim:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Resultados da chamada do kickOff acima, conforme o código acima.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

mas quando você altera seu código para

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Veja bem, o método chama internamente outro método para que não seja interceptado e a saída fique assim:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Você pode ignorar isso fazendo isso

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Trechos de código obtidos em: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

Danyal Sandeelo
fonte
1

Todas as respostas existentes estão corretas, mas acho que não posso dar apenas esse tópico complexo.

Para uma explicação prática e abrangente, você pode dar uma olhada neste guia do Spring @Transactional In-Depth , que tenta o melhor para cobrir o gerenciamento de transações em ~ 4000 palavras simples, com muitos exemplos de código.

Marco Behler
fonte
Foi ótimo ...
Alpit Anand