Frühling - @Transactional - Was passiert im Hintergrund?

334

Ich möchte wissen, was tatsächlich passiert, wenn Sie eine Methode mit Anmerkungen versehen @Transactional. Natürlich weiß ich, dass Spring diese Methode in eine Transaktion einbinden wird.

Aber ich habe folgende Zweifel:

  1. Ich habe gehört, dass Spring eine Proxy-Klasse erstellt ? Kann dies jemand in mehr erklärt Tiefe . Was befindet sich tatsächlich in dieser Proxy-Klasse? Was passiert mit der eigentlichen Klasse? Und wie kann ich Spring's erstellte Proxy-Klasse sehen?
  2. Ich habe auch in Spring Docs gelesen, dass:

Hinweis: Da dieser Mechanismus auf Proxys basiert, werden nur 'externe' Methodenaufrufe abgefangen, die über den Proxy eingehen . Dies bedeutet, dass 'Selbstaufruf', dh eine Methode innerhalb des Zielobjekts, die eine andere Methode des Zielobjekts aufruft, zur Laufzeit nicht zu einer tatsächlichen Transaktion führt, selbst wenn die aufgerufene Methode mit markiert ist @Transactional!

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

Warum werden nur externe Methodenaufrufe unter Transaktion und nicht die Selbstaufrufmethoden angezeigt?

Peakit
quelle
2
Relevante Diskussion ist hier: stackoverflow.com/questions/3120143/…
dma_k

Antworten:

255

Das ist ein großes Thema. Das Spring-Referenzdokument widmet ihm mehrere Kapitel. Ich empfehle, diejenigen zu aspektorientierter Programmierung und Transaktionen zu lesen , da die deklarative Transaktionsunterstützung von Spring AOP als Grundlage verwendet.

Auf einer sehr hohen Ebene erstellt Spring jedoch Proxys für Klassen, die @Transactional für die Klasse selbst oder für Mitglieder deklarieren . Der Proxy ist zur Laufzeit meist unsichtbar. Es bietet Spring die Möglichkeit, Verhaltensweisen vor, nach oder um Methodenaufrufe in das zu proxyierende Objekt einzufügen. Das Transaktionsmanagement ist nur ein Beispiel für das Verhalten, das eingebunden werden kann. Sicherheitsüberprüfungen sind ein weiteres. Und Sie können auch Ihre eigenen für Dinge wie die Protokollierung bereitstellen. Wenn Sie also eine Methode mit @Transactional mit Anmerkungen versehen , erstellt Spring dynamisch einen Proxy, der dieselben Schnittstellen implementiert wie die Klasse, die Sie mit Anmerkungen versehen. Und wenn Clients Anrufe in Ihr Objekt tätigen, werden die Anrufe abgefangen und das Verhalten über den Proxy-Mechanismus injiziert.

Transaktionen in EJB funktionieren übrigens ähnlich.

Wie Sie bereits bemerkt haben, funktioniert der Proxy-Mechanismus nur, wenn Anrufe von einem externen Objekt eingehen. Wenn Sie einen internen Anruf innerhalb des Objekts tätigen, tätigen Sie einen Anruf über die Referenz " this ", die den Proxy umgeht. Es gibt jedoch Möglichkeiten, dieses Problem zu umgehen. Ich erkläre einen Ansatz in diesem Forumsbeitrag, in dem ich einen BeanFactoryPostProcessor verwende , um eine Instanz des Proxys zur Laufzeit in "selbstreferenzierende" Klassen einzufügen . Ich speichere diesen Verweis auf eine Mitgliedsvariable namens " me ". Wenn ich dann interne Aufrufe tätigen muss, die eine Änderung des Transaktionsstatus des Threads erfordern, leite ich den Aufruf über den Proxy (z. B. " me.someMethod ()".".) Der Forumsbeitrag erklärt dies ausführlicher. Beachten Sie, dass der BeanFactoryPostProcessor- Code jetzt etwas anders wäre, da er im Zeitrahmen von Spring 1.x geschrieben wurde. Aber hoffentlich gibt er Ihnen eine Idee. Ich habe eine aktualisierte Version, die Ich könnte wahrscheinlich zur Verfügung stellen.

Rob H.
quelle
4
>> Der Proxy ist zur Laufzeit meistens unsichtbar Oh !! Ich bin neugierig sie zu sehen :) Ruhe .. deine Antwort war sehr umfassend. Dies ist das zweite Mal, dass du mir hilfst. Danke für all die Hilfe.
Peakit
17
Kein Problem. Sie können den Proxy-Code sehen, wenn Sie mit einem Debugger durchgehen. Das ist wahrscheinlich der einfachste Weg. Es gibt keine Magie; Sie sind nur Klassen innerhalb der Spring-Pakete.
Rob H
Wenn die Methode mit der Annotation @Transaction eine Schnittstelle implementiert, verwendet der Spring die dynamische Proxy-API, um die Transaktionsaktion zu injizieren, und verwendet keine Proxys. Ich bevorzuge auf jeden Fall, dass meine transaktionalisierten Klassen Schnittstellen implementieren.
Michael Wiles
1
Ich habe auch das „Ich“ -Schema gefunden (indem ich explizite Verkabelung verwendet habe, um es so zu machen, wie es meiner Meinung nach passt), aber ich denke, wenn Sie es so machen, ist es wahrscheinlich besser, wenn Sie es umgestalten, damit Sie es nicht tun müssen, zu ... haben. Aber ja, das kann manchmal sehr umständlich sein!
Donal Fellows
2
2019: Da diese Antwort in die Jahre gekommen ist, ist der verwiesene Forumsbeitrag nicht mehr verfügbar, der den Fall beschreibt, in dem Sie einen internen Aufruf innerhalb des Objekts tätigen müssen, ohne den Proxy zu umgehenBeanFactoryPostProcessor . Es gibt jedoch eine (meiner Meinung nach) sehr ähnliche Methode, die in dieser Antwort beschrieben wird: stackoverflow.com/a/11277899/3667003 ... und weitere Lösungen im gesamten Thread.
Z3d4s
195

Wenn Spring Ihre Bean-Definitionen lädt und so konfiguriert wurde, dass nach @TransactionalAnmerkungen gesucht wird, werden diese Proxy-Objekte um Ihre eigentliche Bean erstellt . Diese Proxy-Objekte sind Instanzen von Klassen, die zur Laufzeit automatisch generiert werden. Das Standardverhalten dieser Proxy-Objekte beim Aufrufen einer Methode besteht darin, dieselbe Methode nur für die "Ziel" -Bohne (dh Ihre Bean) aufzurufen.

Die Proxys können jedoch auch mit Interceptors ausgestattet werden. Wenn diese Interceptors vorhanden sind, werden sie vom Proxy aufgerufen, bevor die Methode Ihrer Ziel-Bean aufgerufen wird. Für mit Bots versehene Ziel-Beans erstellt @TransactionalSpring ein TransactionInterceptorund übergibt es an das generierte Proxy-Objekt. Wenn Sie also die Methode aus dem Clientcode aufrufen, rufen Sie die Methode für das Proxy-Objekt auf, das zuerst die Methode aufruft TransactionInterceptor(die eine Transaktion startet), die wiederum die Methode für Ihre Ziel-Bean aufruft. Wenn der Aufruf abgeschlossen ist, wird TransactionInterceptordie Transaktion festgeschrieben / zurückgesetzt. Es ist transparent für den Client-Code.

Was die "externe Methode" betrifft, wenn Ihre Bean eine ihrer eigenen Methoden aufruft, wird dies nicht über den Proxy geschehen. Denken Sie daran, Spring wickelt Ihre Bohne in den Proxy, Ihre Bohne hat keine Kenntnis davon. Nur Anrufe von "außerhalb" Ihrer Bean gehen über den Proxy.

Hilft das?

Skaffman
quelle
36
> Denken Sie daran, Spring wickelt Ihre Bohne in den Proxy ein, Ihre Bohne hat keine Kenntnis davon. Dies sagte alles. Was für eine großartige Antwort. Danke fürs Helfen.
Peakit
Tolle Erklärung für den Proxy und die Interceptors. Jetzt verstehe ich, dass Spring ein Proxy-Objekt implementiert, um Aufrufe an eine Ziel-Bean abzufangen. Vielen Dank!
Dharag
Ich denke, Sie versuchen, dieses Bild der Spring-Dokumentation zu beschreiben, und dieses Bild zu sehen, hilft mir sehr: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun
44

Als visuelle Person mag ich es, mit einem Sequenzdiagramm des Proxy-Musters abzuwägen. Wenn Sie nicht wissen, wie man die Pfeile liest, lese ich den ersten wie Clientfolgt : führt aus Proxy.method().

  1. Der Client ruft aus seiner Sicht eine Methode für das Ziel auf und wird vom Proxy stillschweigend abgefangen
  2. Wenn ein Vorher-Aspekt definiert ist, wird er vom Proxy ausgeführt
  3. Dann wird die eigentliche Methode (Ziel) ausgeführt
  4. After-Return und After-Throwing sind optionale Aspekte, die ausgeführt werden, nachdem die Methode zurückgegeben wurde und / oder wenn die Methode eine Ausnahme auslöst
  5. Danach führt der Proxy den After-Aspekt aus (falls definiert).
  6. Schließlich kehrt der Proxy zum aufrufenden Client zurück

Proxy-Muster-Sequenzdiagramm (Ich durfte das Foto unter der Bedingung veröffentlichen, dass ich seine Herkunft erwähnte. Autor: Noel Vaes, Website: www.noelvaes.eu)

progonkpa
quelle
27

Die einfachste Antwort lautet:

Bei jeder Methode, die Sie deklarieren @Transactional, beginnt und endet die Grenze der Transaktion, wenn die Methode abgeschlossen ist.

Wenn Sie einen JPA-Aufruf verwenden, befinden sich alle Commits in dieser Transaktionsgrenze .

Nehmen wir an, Sie speichern Entity1, Entity2 und Entity3. Jetzt , während entity3 Speicher eine Ausnahme auftreten , dann als enitiy1 und entity2 in derselben Transaktion kommt so entity1 und entity2 sein Rollback mit entity3.

Transaktion:

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

Jede Ausnahme führt zum Rollback aller JPA-Transaktionen mit DB. Intern werden JPA-Transaktionen von Spring verwendet.

RoshanKumar Mutha
quelle
2
"Eine Ausnahme führt zum Rollback aller JPA-Transaktionen mit DB." Hinweis Nur RuntimeException führt zu einem Rollback. Überprüfte Ausnahmen führen nicht zu einem Rollback.
Arjun
2

Es mag spät sein, aber ich bin auf etwas gestoßen, das Ihre Bedenken in Bezug auf den Proxy erklärt (nur "externe" Methodenaufrufe, die über den Proxy eingehen, werden abgefangen).

Zum Beispiel haben Sie eine Klasse, die so aussieht

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

und Sie haben einen Aspekt, der so aussieht:

@Component
@Aspect
public class CrossCuttingConcern {

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

Wenn Sie es so ausführen:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

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

}}

Ergebnisse des Aufrufs von kickOff über dem oben angegebenen Code.

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

aber wenn Sie Ihren Code ändern

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

Sie sehen, die Methode ruft intern eine andere Methode auf, damit sie nicht abgefangen wird und die Ausgabe folgendermaßen aussehen würde:

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

Sie können dies umgehen, indem Sie dies tun

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

Code-Schnipsel aus: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

Danyal Sandeelo
quelle
0

Alle vorhandenen Antworten sind richtig, aber ich glaube, ich kann nicht nur dieses komplexe Thema geben.

Eine umfassende, praktische Erklärung finden Sie in diesem ausführlichen Spring @Transactional- Handbuch, in dem versucht wird, das Transaktionsmanagement in ~ 4000 einfachen Worten mit vielen Codebeispielen zu behandeln.

Marco Behler
quelle