Primavera - @Transactional - Cosa succede in background?


334

Voglio sapere cosa succede realmente quando annoti un metodo con @Transactional? Certo, so che Spring includerà quel metodo in una Transazione.

Ma ho i seguenti dubbi:

  1. Ho sentito che Spring crea una classe proxy ? Qualcuno può spiegare questo in modo più approfondito . Cosa risiede effettivamente in quella classe proxy? Cosa succede alla classe attuale? E come posso vedere la classe proxy creata da Spring
  2. Ho anche letto nei documenti di primavera che:

Nota: poiché questo meccanismo si basa su proxy, verranno intercettate solo le chiamate di metodo "esterne" che arrivano attraverso il proxy . Ciò significa che "auto-invocazione", ovvero un metodo all'interno dell'oggetto target che chiama un altro metodo dell'oggetto target, non porterà a una transazione effettiva in fase di esecuzione anche se il metodo invocato è contrassegnato con @Transactional!

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

Perché solo le chiamate di metodo esterne saranno sotto Transazione e non i metodi di auto-invocazione?


2
Discussione Rilevante è qui: stackoverflow.com/questions/3120143/...
dma_k

Risposte:


255

Questo è un argomento importante. Il documento di riferimento Spring vi dedica più capitoli. Consiglio di leggere quelli sulla programmazione e le transazioni orientate agli aspetti , poiché il supporto per le transazioni dichiarative di Spring utilizza AOP alla sua fondazione.

Ma a un livello molto alto, Spring crea proxy per le classi che dichiarano @Transactional sulla classe stessa o sui membri. Il proxy è per lo più invisibile in fase di esecuzione. Fornisce un modo per Spring di iniettare comportamenti prima, dopo o intorno alle chiamate di metodo nell'oggetto sottoposto a proxy. La gestione delle transazioni è solo un esempio dei comportamenti che possono essere collegati. I controlli di sicurezza sono un altro. E puoi anche fornire il tuo per cose come la registrazione. Quindi, quando annoti un metodo con @Transactional , Spring crea dinamicamente un proxy che implementa le stesse interfacce della classe che stai annotando. E quando i client effettuano chiamate nel tuo oggetto, le chiamate vengono intercettate e i comportamenti vengono iniettati tramite il meccanismo proxy.

Le transazioni nel bean funzionano in modo simile, tra l'altro.

Come hai osservato, attraverso, il meccanismo proxy funziona solo quando arrivano chiamate da qualche oggetto esterno. Quando si effettua una chiamata interna all'interno dell'oggetto, si effettua una chiamata tramite il riferimento " this ", che ignora il proxy. Ci sono modi per aggirare quel problema, tuttavia. Spiego un approccio in questo post del forum in cui utilizzo un BeanFactoryPostProcessor per iniettare un'istanza del proxy in classi "autoreferenziali" in fase di esecuzione. Salvo questo riferimento a una variabile membro denominata " me ". Quindi, se devo effettuare chiamate interne che richiedono una modifica dello stato della transazione del thread, indirizzo la chiamata tramite il proxy (ad esempio " me.someMethod () ".) Il post sul forum spiega in modo più dettagliato. Si noti che BeanFactoryPostProcessoril codice sarebbe un po 'diverso ora, poiché è stato riscritto nel periodo di primavera 1.x. Ma spero che ti dia un'idea. Ho una versione aggiornata che potrei probabilmente rendere disponibile.


4
>> Il proxy è per lo più invisibile in fase di esecuzione Oh !! Sono curioso di vederli :) Riposo .. la tua risposta è stata molto esaustiva. Questa è la seconda volta che mi stai aiutando ... Grazie per tutto l'aiuto.
Peakit

17
Nessun problema. È possibile visualizzare il codice proxy se si passa attraverso un debugger. Questo è probabilmente il modo più semplice. Non c'è magia; sono solo lezioni all'interno dei pacchetti Spring.
Rob H

E se il metodo che ha l'annotazione @Transaction sta implementando un'interfaccia, la primavera utilizzerà l'API proxy dinamica per iniettare la transazione e non usare i proxy. Preferisco che le mie classi transazionalizzate implementino interfacce in ogni caso.
Michael Wiles,

1
Ho trovato anche lo schema "io" (usando il cablaggio esplicito per farlo come si adatta al mio modo di pensare), ma penso che se lo fai in quel modo, probabilmente starai meglio con il refactoring in modo da non farlo dovere. Ma sì, a volte potrebbe essere molto imbarazzante!
Donal Fellows,

2
2019: man mano che questa risposta invecchia, il post del forum di riferimento non è più disponibile, il che descrive il caso in cui è necessario effettuare una chiamata interna all'interno dell'oggetto senza bypassare il proxy, utilizzandoBeanFactoryPostProcessor . Tuttavia, esiste un metodo (a mio avviso) molto simile descritto in questa risposta: stackoverflow.com/a/11277899/3667003 ... e anche altre soluzioni nell'intero thread.
Z3d4

196

Quando Spring carica le definizioni dei bean ed è stato configurato per la ricerca @Transactional annotazioni, creerà questi oggetti proxy attorno al bean effettivo . Questi oggetti proxy sono istanze di classi generate automaticamente in fase di esecuzione. Il comportamento predefinito di questi oggetti proxy quando viene invocato un metodo è solo quello di invocare lo stesso metodo sul bean "target" (ovvero il proprio bean).

Tuttavia, i proxy possono anche essere forniti con intercettori e, quando presenti, questi intercettori saranno invocati dal proxy prima che invochi il metodo del bean di destinazione. Per i bean di destinazione annotati con@Transactional , Spring creerà un TransactionInterceptore lo passerà all'oggetto proxy generato. Pertanto, quando si chiama il metodo dal codice client, si chiama il metodo sull'oggetto proxy, che per primo richiama il TransactionInterceptor(che inizia una transazione), che a sua volta invoca il metodo sul proprio bean di destinazione. Al termine dell'invocazione, il TransactionInterceptorcommit / rollback della transazione. È trasparente per il codice client.

Per quanto riguarda la cosa "metodo esterno", se il tuo bean invoca uno dei suoi metodi, non lo farà tramite il proxy. Ricorda, Spring avvolge il tuo bean nel proxy, il tuo bean non ne è a conoscenza. Solo le chiamate "esterne" al tuo bean passano attraverso il proxy.

Questo aiuta?


36
> Ricorda, Spring avvolge il tuo bean nel proxy, il tuo bean non ne è a conoscenza. Ha detto tutto. Che risposta eccezionale. Grazie dell'aiuto.
Peakit

Grande spiegazione, per il proxy e gli intercettori. Ora capisco primavera implementare un oggetto proxy per intercettare le chiamate a un bean di destinazione. Grazie!
Dharag,

Penso che tu stia cercando di descrivere questa immagine della documentazione di primavera e vedere questa immagine mi aiuta molto: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun

44

Come persona visiva, mi piace ponderare con un diagramma di sequenza del modello proxy. Se non sai come leggere le frecce, leggo la prima in questo modo: Clientesegue Proxy.method().

  1. Il client chiama un metodo sulla destinazione dal suo punto di vista e viene silenziosamente intercettato dal proxy
  2. Se viene definito un aspetto precedente, il proxy lo eseguirà
  3. Quindi, viene eseguito il metodo effettivo (target)
  4. After-return e after-lancio sono aspetti opzionali che vengono eseguiti dopo la restituzione del metodo e / o se il metodo genera un'eccezione
  5. Successivamente, il proxy esegue l'aspetto after (se definito)
  6. Alla fine il proxy ritorna al client chiamante

Diagramma di sequenza del modello proxy (Mi è stato permesso di pubblicare la foto a condizione che ne citassi le origini. Autore: Noel Vaes, sito web: www.noelvaes.eu)


27

La risposta più semplice è:

Su qualsiasi metodo si dichiara @Transactionalil limite di inizio e fine della transazione al termine del metodo.

Se si utilizza la chiamata JPA, tutti i commit sono in questo limite di transazione .

Diciamo che stai salvando entity1, entity2 ed entity3. Ora, mentre il risparmio entity3 un un'eccezione si verificano , poi come enitiy1 e entity2 realizzato negli stessi dell'operazione l'Entity1 e entity2 saranno rollback con entity3.

Transazione:

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

Qualsiasi eccezione comporterà il rollback di tutte le transazioni JPA con DB. Le transazioni JPA interne vengono utilizzate da Spring.


2
"Un'eccezione A̶n̶y̶ comporterà il rollback di tutte le transazioni JPA con DB." Nota Solo RuntimeException comporta il rollback. Eccezioni verificate, non comporteranno il rollback.
Arjun,

2

Potrebbe essere tardi, ma mi sono imbattuto in qualcosa che spiega la tua preoccupazione relativa al proxy (verranno intercettate solo le chiamate di metodo "esterne" che arrivano attraverso il proxy).

Ad esempio, hai una classe simile a questa

@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 hai un aspetto simile a questo:

@Component
@Aspect
public class CrossCuttingConcern {

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

Quando lo esegui in questo modo:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

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

}

Risultati della chiamata di kickOff sopra il codice sopra indicato.

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

ma quando cambi il codice in

@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);
}

Vedete, il metodo chiama internamente un altro metodo in modo che non venga intercettato e l'output sarà simile al seguente:

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

Puoi aggirare ciò facendo quello

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

Frammenti di codice tratti da: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


1

Tutte le risposte esistenti sono corrette, ma ritengo che non possa dare solo questo argomento complesso.

Per una spiegazione pratica e completa potresti voler dare un'occhiata a questa guida approfondita di Spring @Transactional , che fa del suo meglio per coprire la gestione delle transazioni in circa 4000 semplici parole, con molti esempi di codice.


È stato fantastico ...
Alpit Anand
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.