Wie schreibe ich korrekten / zuverlässigen Transaktionscode mit JAX-RS und Spring?

8

Grundsätzlich versuche ich zu verstehen, wie man den Transaktionscode richtig schreibt (oder "richtig schreibt"), wenn man den REST-Dienst mit Jax-RS und Spring entwickelt. Außerdem verwenden wir JOOQ für den Datenzugriff. Aber das sollte nicht sehr relevant sein.
Betrachten wir das einfache Modell, in dem wir einige Organisationen haben, die diese Felder haben: "id", "name", "code" . Alle müssen einzigartig sein. Außerdem gibt es ein status -Feld.
Die Organisation könnte irgendwann entfernt werden. Aber wir wollen die Daten nicht komplett entfernen, weil wir sie für Analyse- / Wartungszwecke speichern wollen. Also setzen wir das Organisationsstatusfeld auf 'REMOVED' .
Da wir die Organisationszeile nicht aus der Tabelle löschen, können wir die eindeutige Einschränkung nicht einfach auf die Spalte "Name" anwenden, da wir möglicherweise die Organisation löschen und dann eine neue mit dem gleichen Namen erstellen. Aber nehmen wir an, dass Codes global eindeutig sein müssen, also haben wir eine einzigartige Einschränkung für die Spalte code .

Sehen wir uns also dieses einfache Beispiel an, das eine Organisation erstellt und einige Prüfungen auf dem Weg durchführt.

Ressource:

%Vor%

DAO-Dienst sieht so aus:

%Vor%

Das bringt einige Fragen:

  1. Soll ich @Transactional Annotation in die Methode createOrganization der Ressource einfügen? Oder sollte ich einen weiteren Dienst erstellen, der mit DAO kommuniziert und @Transactional Annotation zu seiner Methode hinzufügt? Etwas anderes?
  2. Was passiert, wenn zwei Benutzer gleichzeitig eine Anfrage mit demselben "code" -Feld senden? Bevor die erste Transaktion ausgeführt wird, werden die Überprüfungen erfolgreich durchgeführt, sodass keine 409 Rückmeldungen gesendet werden. Als erste Transaktion wird ordnungsgemäß übergeben, aber die zweite Transaktion wird die DB-Einschränkung verletzen. Dies wird SQLException auslösen. Wie man das anmutig handhabt? Ich meine, ich möchte immer noch eine nette Fehlermeldung auf der Client-Seite zeigen, dass der Name bereits benutzt wird. Aber ich kann SQLException oder etw nicht wirklich parsen. Kann ich?
  3. Ähnlich wie der vorherige, aber diesmal ist "name" nicht eindeutig. In diesem Fall verletzt die zweite Transaktion keine Beschränkungen, was dazu führt, dass zwei Organisationen denselben Namen haben und unsere Geschäftsbeschränkungen verletzen.
  4. Wo kann ich Tutorials / Code / etc. sehen / lernen, die Sie als gute Beispiele betrachten, wie man korrekten / zuverlässigen REST + DB-Code mit komplizierter Geschäftslogik schreibt. Github / Bücher / Blogs, was auch immer. Ich habe versucht, so etwas zu finden, aber die meisten Beispiele konzentrieren sich nur auf die Installation - fügen Sie diese Bibliotheken hinzu, verwenden Sie diese Anmerkungen, es gibt Ihre einfache CRUD, das Ende. Sie enthalten keinerlei Transaktionskosten. Ie.

UPDATE: Ich kenne die Isolationsstufe und die übliche Fehler / Isolationsmatrix (Dirty Reads, etc ..). ). Das Problem, das ich habe, ist, einige "produktionsreife" Beispiele zu finden, von denen ich lernen kann. Oder ein gutes Buch zu einem Thema. Ich bekomme immer noch nicht wirklich, wie man alle Fehler richtig behandelt .. Ich denke, ich muss ein paar Mal wiederholen, wenn die Transaktion fehlgeschlagen .. und dann nur einige generische Fehler und implementieren Client, der das handhabt .. Aber tun Ich muss wirklich den SERIALIZABLE-Modus verwenden, wenn ich Bereichsabfragen verwende? Weil es die Leistung stark beeinträchtigt. Aber sonst wie kann ich garantieren, dass die Transaktion fehlschlägt ..

Wie auch immer, ich habe beschlossen, dass ich im Moment mehr Zeit brauche, um etwas über Transaktionen und das DB-Management zu lernen, um dieses Problem anzugehen ...

    
Dzmitry Paulenka 19.08.2016, 16:57
quelle

4 Antworten

1

Im Allgemeinen sollte der Endpunkt, ohne über Transaktionalität zu sprechen, nur Parameter aus der Anfrage abrufen und den Service anrufen. Es sollte keine Geschäftslogik machen.

Es scheint, dass Ihre checkXXX-Methoden Teil der Geschäftslogik sind, weil sie Fehler bei domänenspezifischen Konflikten auslösen. Warum sollte man sie nicht in eine einzige Methode einfügen, die übrigens transaktional ist?

%Vor%

Ich habe genommen, da Ihre Parameter Strings sind, aber sie können alles sein. Ich bin mir nicht sicher, ob Sie Responses.abortConflict in der Service-Schicht werfen wollen, weil es sich um ein REST-Konzept handelt, aber Sie können bei Bedarf eigene Ausnahme-Typen definieren.

Der Endpunktcode sollte so aussehen, er könnte jedoch einen zusätzlichen try-catch-Block enthalten, der die geworfenen Ausnahmen in Fehlerreaktionen umwandelt:

%Vor%

Wie bei Frage 2 und 3 sind Transaktionsisolationsstufen Ihre Freunde. Stellen Sie die Isolationsstufe hoch genug ein. Ich denke, wiederholbares Lesen ist in Ihrem Fall das Richtige. Ihre checkXXX-Methoden erkennen, ob eine andere Transaktion Entitäten mit demselben Namen oder Code festschreibt, und garantiert, dass die Situationen bis zur Ausführung der create-Methode bestehen bleiben. Ein weiteres nützliches Lesen bezüglich Spring- und Transaktionsisolationslevels.

    
pcjuzer 22.08.2016 15:15
quelle
1

Nach meinem Verständnis der beste Weg, Transaktion auf DB-Ebene zu behandeln, müssen Sie Spring Isolation Trnsaktion in effektiver Weise in der Dao-Schicht verwenden. Unten ist beispiel industrie standard codde in ihrem fall ...

%Vor%

Bitte zwickt mich, wenn ich hier falsch liege

    
Abhinab Kanrar 24.08.2016 08:57
quelle
0

Trennung von Bedenken:

  • Jax-rs-Ressource (Endpunkt): Behandeln Sie einfach die Anfrage, rufen Sie den Service auf und verpacken Sie die potenzielle Ausnahme in den entsprechenden Antwortcode (fangen und wickeln Sie sie manuell ein oder verwenden Sie Ausnahme-Mapper ).
  • Service- / Business-Schicht: eine Transaktions-Methode für jede Arbeitseinheit verfügbar machen, Geschäftsfehler müssen als geprüfte Ausnahme behandelt werden, operative als unchecked (Unterklassen von RuntimeException ).
  • Datenzugriffsschicht: Behandle einfach den Datenzugriff (z. B. db-Kontext abrufen, Abfrage ausführen und schließlich das Ergebnis zuordnen).

Ich bestehe auf eine Sache, der gute Ort, um Transaktionsgrenzen zu haben, ist der Ort, an dem Ihre Geschäftsmethoden definiert sind. Ein Transaktionsbereich muss eine Geschäftseinheit der Arbeit sein.

Was das Problem der Parallelität betrifft, gibt es zwei Möglichkeiten, um diese Art von Nebenläufigkeitsproblemen zu lösen: pessimistisches oder optimistisches Sperren.

  • Pessimistisch:

    • Sperren
    • mach deine Sachen
    • Aktualisieren
    • Sperre aufheben
  • Optimistisch:

    • Version prüfen
    • mach deine Sachen
    • update, wenn die Version gleich ist, andernfalls fehlschlagen

Pessimistisch ist ein Problem in Bezug auf Skalierbarkeit und Leistung. Das optimistische Problem besteht darin, dass Sie manchmal einen Bedienungsfehler an den Endbenutzer senden.

Ich würde persönlich mit einer optimistischen Sperrung in Ihrem Fall gehen, JOOQ unterstützt es

    
Gab 29.08.2016 16:20
quelle
-1

Zunächst einmal sollte die DAO-Ebene nicht einmal wissen, dass sie von einem REST-Webservice angeboten wird. Stellen Sie sicher, dass Sie die Verantwortlichkeiten voneinander trennen.

Halten Sie die @Transactional auf dem DAO. Wenn Sie nur eine einzige Anweisung ausgeben, müssen Sie entscheiden, ob Sie mit Dirty Reads einverstanden sind. Finde heraus, was die niedrigste Isolationsstufe für deine Anwendung ist. Jede Methode startet eine neue Transaction (sofern nicht von einer anderen Methode aufgerufen, die bereits gestartet wurde) und wenn Exceptions ausgelöst werden, werden alle Aufrufe zurückgesetzt. Sie können einen benutzerdefinierten ExceptionHandler in Ihrem Controller einrichten, um SQLDataIntegrityExceptions zu verarbeiten (wie bei Ihrem Beispielcode).

Verwenden Sie einen Gesamtprimärschlüssel, der (ID, Name, Code, Status) abdeckt, so dass Sie eine Organisation mit demselben Namen haben können, aber einer ist "AKTUELL" und einer wird "ENTFERNT"

sein     
Gandalf 19.08.2016 22:43
quelle