Warum sollten wir keinen Spring MVC Controller @Transactional machen?
Gibt es schon ein paar Fragen zu dem Thema, aber keine Antwort bietet sich wirklich an Argumenten, um zu erklären, warum wir es nicht machen sollte eine Spring MVC-controller Transactional
. Siehe:
- Transaktion nicht korrekt funktioniert - Frühjahr/MyBatis
- Für web-MVC Spring-app sollte @Transactional gehen Sie auf controller oder service?
- Macht Spring 3 MVC-controller-Methode Transaktions -
- Spring MVC-Controller-Transaktions -
So, warum?
- Ist es unüberwindbar technische Probleme?
- Gibt es architektonische Fragen?
- Ist es die Leistung/deadlock/concurrency-Probleme?
- Sind manchmal mehrere separate Transaktionen erforderlich? Wenn ja, was sind die use cases? (Ich mag die Vereinfachung der Konstruktion, dass die Aufrufe von server entweder vollständig gelingen oder vollständig scheitern. Es klingt ein sehr stabiles Verhalten)
Hintergrund:
Ich arbeitete vor ein paar Jahren in einem Team auf einem sehr großen ERP-Software implementiert in C#/NHibernate/Spring.Net. Der roundtrip zum server wurde genau umgesetzt wie, dass: die Transaktion eröffnet wurde, vor dem Eintritt in einen der controller-Logik und begangen wurde oder rollbacked wird nach Beendigung der controller. Die Transaktion konnte in dem Rahmen, so dass niemand hatte sich darum zu kümmern. Es war eine geniale Lösung: stabil, einfach, nur ein paar Architekten hatten, um die Pflege über Transaktion Probleme, der rest des Teams nur features implementiert.
Aus meiner Sicht, es ist das beste design, die ich jemals gesehen habe. Als ich versuchte zu reproduzieren die gleichen design-mit Spring MVC, trat ich in einen Albtraum mit lazy-loading und Transaktion Fragen und jedes mal die gleiche Antwort: machen Sie nicht den Transaktions-controller, aber warum?
Vielen Dank im Voraus für Ihre gegründet Antworten!
InformationsquelleAutor der Frage jeromerg | 2014-04-16
Du musst angemeldet sein, um einen Kommentar abzugeben.
TLDR: dies ist, da nur die service-layer in der Anwendung die Logik, die nötig ist, um zu identifizieren, den Umfang der Datenbank/business-Transaktion. Der controller und Persistenzschicht von design können nicht/sollte nicht wissen, den Umfang einer Transaktion.
Den controller vorgenommen werden können
@Transactional
aber in der Tat, es ist eine gemeinsame Empfehlung, nur die service-Ebene Transaktions - (die Persistenzschicht sollte nicht transaktionalen).Der Grund dafür ist nicht die technische Machbarkeit, sondern die Trennung von Bedenken. Der controller Verantwortung ist, um die parameter fordert, und rufen Sie dann eine oder mehrere service-Methoden und kombinieren der Ergebnisse in eine Antwort, die dann zurück an den client gesendet.
Also der controller hat eine Funktion als Koordinator der Anfrage Ausführung und Transformator der domain-Daten in ein format, das der client nutzen kann, wie DTOs.
Die Geschäftslogik befindet sich auf dem service-layer und die Persistenzschicht nur abrufen /speichern von Daten hin und her aus der Datenbank.
Rahmen einer Datenbank-Transaktion ist wirklich ein business-Konzept, so viel wie ein technisches Konzept: in ein Konto übertragen ein Konto wird nur belastet, wenn der andere gutgeschrieben usw., also nur die service-Schicht enthält die Geschäftslogik kann wirklich wissen, den Umfang einer bank account transfer transaction.
Persistence-layer kann nicht wissen, was die Transaktion ist es, nehmen Sie zum Beispiel eine Methode
customerDao.saveAddress
. Sollte es so laufen, in einem eigenen separaten Transaktion immer? es gibt keine Möglichkeit zu wissen, es kommt auf die business Logik aufgerufen wird. Manchmal ist es sollte laufen auf einer separaten Transaktion, manchmal nur speichern die Daten, wenn dersaveCustomer
auch gearbeitet, etc.Das gleiche gilt für die Steuerung: sollte
saveCustomer
undsaveErrorMessages
gehen Sie in der gleichen Transaktion? Möchten Sie vielleicht sparen sich die Kunden und wenn das fehlschlägt, dann versuchen zu speichern, Fehlermeldungen und Rückgabe einer Fehlermeldung an den client, anstatt zu Rollen zurück alles, einschließlich der Fehlermeldungen, die Sie retten wollte auf die Datenbank.In nicht-Transaktions-Controller, Methoden der Rückkehr aus der service-Ebene zurück detached Entitäten, da die Sitzung geschlossen. Das ist normal, die Lösung ist entweder
OpenSessionInView
oder Abfragen, die eifrig die Ergebnisse abrufen der controller kennt, die es braucht.Having said that, es ist nicht ein Verbrechen zu machen, die Transaktions-Controller, es ist nur nicht die am häufigsten verwendete Praxis.
InformationsquelleAutor der Antwort Angular University
Habe ich gesehen, beide Fälle in der Praxis, in mittleren bis großen Unternehmen web-Anwendungen mit verschiedenen web-frameworks (JSP/Struts 1.x, GWT, JSF 2, mit Java EE und Spring).
In meiner Erfahrung, es ist am besten, der zur Abgrenzung der Transaktionen, die auf der höchsten Ebene, also an der "controller" - Ebene.
In einem Fall hatten wir eine
BaseAction
- Klasse, die sich Struts'Action
Klasse, mit einer Umsetzung für dieexecute(...)
Methode behandelt Hibernate-session-management (gespeichert in einemThreadLocal
Objekt), Transaktion begin/commit/rollback, und die Zuordnung von Ausnahmen benutzerfreundliche Fehlermeldungen. Diese Methode würde einfach rollback der aktuellen Transaktion, wenn eine Ausnahme bekam weitergegeben bis zu dieser Stufe, oder wenn es war markiert für das rollback nur; sonst würde es einen commit für die Transaktion. Dies funktioniert in jedem Fall, wo normalerweise gibt es einen einzigen Datenbank-Transaktion für die gesamte HTTP-request/response-Zyklus. Seltenen Fällen, in denen mehrere Transaktionen nötig waren, behandelt werden würde in use-case-spezifischen code.Im Fall von GWT-RPC, eine ähnliche Lösung wurde implementiert, indem ein Basis-GWT-Servlet-Implementierung.
Mit JSF 2, habe ich bisher nur verwendet, service-level-Abgrenzung (mit EJB Session beans, die automatisch "ERFORDERLICH" transaction propagation). Nachteile gibt es hier, im Gegensatz zu der Abgrenzung von Transaktionen auf der Ebene des JSF-backing-beans. Im Grunde, das problem ist, dass in vielen Fällen die JSF-controller benötigt, um mehrere service-Aufrufe, die jeweils den Zugriff auf die Datenbank der Anwendung. Mit service-level-Transaktionen, dies bedeutet, mehrere separate Transaktionen (alle verpflichtet, es sei denn, eine Ausnahme Auftritt), die Steuern, die mehr den Datenbank-server. Es ist nicht nur ein performance-Nachteil, though. Mehrere Transaktionen für einen einzelnen request/response-Verfahren kann auch führen zu subtilen bugs (ich erinnere mich nicht an die details nicht mehr, nur, dass solche Probleme auftreten, haben).
Andere Antwort auf diese Frage, spricht über "Logik notwendig, um den Umfang der Datenbank/business-Transaktion". Dieses argument macht keinen Sinn für mich, da es keine Logik im Zusammenhang mit der Transaktion Abgrenzung an alle, normalerweise. Weder die controller-Klassen, noch service-Klassen müssen eigentlich "wissen" über Transaktionen. In der überwiegenden Mehrzahl der Fälle, in der eine web-app, die jeder Betrieb tritt innerhalb eines HTTP-request/response-paar, die mit dem Umfang der Transaktion werden alle einzelnen Operationen ausgeführt wird, von dem Punkt, dass die Anfrage eingegangen ist, bis die Reaktion abgeschlossen werden.
Occasionaly, business-service oder controller kann eine Ausnahme behandeln müssen, in einer bestimmten Weise, dann wohl daneben die aktuelle Transaktion rollback nur. In Java EE (JTA), dies wird durch den Aufruf UserTransaction#setRollbackOnly(). Die
UserTransaction
- Objekt injiziert werden kann in eine@Resource
Feld, oder erhalten programmgesteuert von einigenThreadLocal
. Im Frühjahr, die@Transactional
Anmerkung können rollback angegeben werden, die für bestimmte exception-Typen, oder den code erhalten Sie eine thread-lokale TransactionStatus und rufensetRollbackOnly()
.So, in meiner Meinung und Erfahrung, so dass die Transaktions-controller ist der bessere Ansatz.
InformationsquelleAutor der Antwort Rogério
Manchmal möchten Sie ein Rollback einer Transaktion, wenn eine Ausnahme geworfen wird, aber zur gleichen Zeit, die Sie behandeln möchten der Ausnahme, erstellen Sie eine richtige Antwort in der Steuerung.
Wenn Sie@Transactional
auf der controller-Methode die einzige Möglichkeit zur Durchsetzung der rollback zu werfen die Transaktion von der controller-Methode, aber dann können Sie nicht zurück eine normale Reaktion-Objekt.Update: Ein rollback kann auch erreicht werden, programmgesteuert, wie in Rodério Antwort.
Eine bessere Lösung ist es, Ihre service-Methode, Transaktions-und dann Griff eine mögliche Ausnahme in der controller-Methoden.
Das folgende Beispiel zeigt eine user-service mit einem
createUser
Methode, dass die Methode verantwortlich ist, den Benutzer zu erstellen und senden Sie eine E-Mail an den Benutzer. Wenn das versenden der mail schlägt fehl, wir wollen ein rollback für die Erstellung des Benutzers:Dann in deinem controller-Sie können wickeln Sie den Aufruf
createUser
in einem try/catch, und erstellen Sie eine richtige Antwort für den Benutzer:Wenn Sie eine
@Transaction
Sie auf Ihrem controller die Methode, dass ist einfach nicht möglich.InformationsquelleAutor der Antwort lanoxx