Meine Anwendung lädt eine Liste von Entitäten, die verarbeitet werden sollen. Dies geschieht in einer Klasse, die einen Scheduler verwendet
%Vor% In meinem HandlingService
wird jede Aufgabe in der Isolation mit REQUIRES_NEW
für die Verbreitungsebene verarbeitet.
Der Code funktioniert nur, weil ich die übergeordnete Transaktion in TaskScheduler
class gestartet habe. Wenn ich die @Transactional
Annotation lösche, werden die Entitäten nicht mehr verwaltet und die Aktualisierung der Task-Entität wird nicht an die db weitergegeben. Ich finde es nicht natürlich, die geplante Methode transaktional zu machen.
Von dem, was ich sehe, habe ich zwei Möglichkeiten:
1. Behalten Sie den Code so, wie er heute ist.
2. Entfernen Sie die @Transactional
-Anmerkung aus dem Scheduler, übergeben Sie die ID der Aufgabe und laden Sie die Aufgabenentität im HandlingService neu.
@Async
ausgeführt werden
Können Sie mir bitte Ihre Meinung dazu geben, wie Sie diese Art von Problemen richtig lösen können, vielleicht mit einer anderen Methode, die ich nicht kannte?
Wenn Sie beabsichtigen, jede Aufgabe in einer separaten Transaktion zu bearbeiten, funktioniert Ihr erster Ansatz tatsächlich nicht, da alles am Ende der Scheduler-Transaktion festgeschrieben ist.
Der Grund dafür ist, dass Task
-Instanzen in den verschachtelten Transaktionen im Grunde losgelöste Entitäten sind ( Session
s, die in den verschachtelten Transaktionen gestartet wurden, kennen diese Instanzen nicht). Am Ende der Scheduler-Transaktion führt Hibernate eine Dirty-Prüfung für die verwalteten Instanzen durch und synchronisiert die Änderungen mit der Datenbank.
Dieser Ansatz ist auch sehr riskant, da es Probleme geben kann, wenn Sie versuchen, auf einen nicht initialisierten Proxy in einer Task
-Instanz in der verschachtelten Transaktion zuzugreifen. Und es kann Probleme geben, wenn Sie das Objektobjekt Task
in der verschachtelten Transaktion ändern, indem Sie eine andere in der verschachtelten Transaktion geladene Entitätsinstanz hinzufügen (weil diese Instanz jetzt getrennt wird, wenn das Steuerelement zur Scheduler-Transaktion zurückkehrt).
Auf der anderen Seite ist Ihr zweiter Ansatz korrekt und einfach und hilft, alle oben genannten Fallstricke zu vermeiden. Nur, ich würde die IDs lesen und die Transaktion festschreiben (es muss nicht während der Verarbeitung der Aufgaben ausgesetzt werden). Der einfachste Weg, dies zu erreichen, besteht darin, die Annotation Transactional
aus dem Scheduler zu entfernen und die Repository-Methode transaktional zu machen (falls sie noch nicht transaktional ist).
Wenn (und nur wenn) die Leistung des zweiten Ansatzes ein Problem ist, könnten Sie, wie Sie bereits erwähnten, mit der asynchronen Verarbeitung fortfahren oder sogar die Verarbeitung bis zu einem gewissen Grad parallelisieren. Sie können auch einen Blick auf erweiterte Sitzungen werfen (Gespräche), vielleicht finden Sie es für Ihren Anwendungsfall geeignet.
Der aktuelle Code verarbeitet die Aufgabe in der verschachtelten Transaktion, aktualisiert jedoch den Status der Aufgabe in der äußeren Transaktion (weil das Task-Objekt von der äußeren Transaktion verwaltet wird). Da es sich um unterschiedliche Transaktionen handelt, ist es möglich, dass einer erfolgreich ausgeführt wird, während der andere fehlschlägt und die Datenbank in einem inkonsistenten Zustand verbleibt. Insbesondere bleiben mit diesem Code abgeschlossene Aufgaben im Status offen, wenn die Verarbeitung einer anderen Aufgabe eine Ausnahme auslöst oder der Server neu gestartet wird, bevor alle Aufgaben verarbeitet wurden.
Wie Ihr Beispiel zeigt, macht die Übergabe von verwalteten Entitäten an eine andere Transaktion es zweideutig, welche Transaktion diese Entitäten aktualisieren sollte, und wird daher am besten vermieden. Stattdessen sollten Sie IDs (oder losgelöste Entitäten) übergeben und unnötige Verschachtelung von Transaktionen vermeiden.
Angenommen, processTask(task);
ist eine Methode in der Klasse HandlingService
(entspricht der handle(task)
-Methode), das Entfernen von @Transactional
in HandlingService
funktioniert aufgrund des natürlichen Verhaltens von Spring's dynamischem Proxy nicht.
Zitieren von spring.io forum :
Wenn Spring Ihren TestService lädt, wird er mit einem Proxy umschlossen. Wenn Sie eine Methode des TestService außerhalb von TestService aufrufen, wird stattdessen der Proxy aufgerufen, und Ihre Transaktion wird ordnungsgemäß verwaltet. Wenn Sie jedoch Ihre Transaktionsmethode in einer Methode im selben Objekt aufrufen, rufen Sie nicht den Proxy, sondern direkt das Ziel des Proxys auf, und Sie führen den Code, der sich um Ihren Service wickelt, nicht um die Transaktion zu verwalten.
Dies ist einer der SO-Threads zu diesem Thema, und hier sind einige Artikel dazu:
Wenn Sie @Transaction
Annotation wirklich nicht gerne in Ihre @Scheduled
-Methode einfügen möchten, könnten Sie eine Transaktion von EntityManager erhalten und die Transaktion programmgesteuert verwalten, zum Beispiel:
Aber ich bezweifle, dass Sie diesen Weg nehmen werden (nun, ich werde nicht). Am Ende,
Können Sie mir bitte Ihre Meinung dazu geben, wie Sie diese Art von Problemen richtig lösen können?
In diesem Fall gibt es keinen richtigen Weg . Meine persönliche Meinung ist, Annotation (zum Beispiel @Transactional
) ist nur ein Marker , und Sie benötigen einen Annotationsprozessor (in diesem Fall Spring), um @Transactional
arbeiten zu lassen. Die Annotation hat ohne den Prozessor keinerlei Auswirkungen.
Ich werde mir mehr Sorgen machen, zum Beispiel, warum ich processTask(task)
und task.setStatus(Status.PROCESSED);
außerhalb von processTask(task)
live habe, wenn es so aussieht, als würde es das gleiche tun, etc.
HTH.
Tags und Links java jpa spring-data-jpa hibernate spring-data