3 Stimmen

Richtige Möglichkeit, um herauszufinden, was in einem Spring-MVC-Controller durch einen Beitrag geändert wurde?

Es ist eine eher allgemeine Frage, aber ich werde ein vereinfachtes Beispiel geben. Angenommen, ich habe eine Web-CRUD-Anwendung, die einfache Entitäten in einer Datenbank verwaltet, nichts als klassisch: JSP-Ansicht, RequestMapping-annotierter Controller, transaktionale Service-Schicht und DAO.
Bei einem Update muss ich die vorherigen Werte meiner Felder kennen, weil eine Geschäftsregel einen Test verlangt, der die alten und neuen Werte beinhaltet.

Also suche ich nach bewährten Verfahren für diesen Anwendungsfall.

Ich denke, der Spring-Code ist viel umfangreicher getestet und robuster als meiner, und ich würde es gerne so weit wie möglich auf die Spring-Art machen.

Hier ist, was ich versucht habe:

1/ Laden Sie ein leeres Objekt im Controller und verwalten Sie das Update im Service:
Data.java:

Klasse Data {
    int id; // Primärschlüssel
    String name;
    // ... andere Felder, Getter und Setter aus Platzgründen ausgelassen
}

DataController

...
@RequestMapping("/data/edit/{id}", method=RequestMethod.GET)
public String edit(@PathVariable("id") int id, Model model) {
    model.setAttribute("data", service.getData(id);
    return "/data/edit";
}
@RequestMapping("/data/edit/{id}", method=RequestMethod.POST)
public String update(@PathVariable("id") int id, @ModelAttribute Data data, BindingResult result) {
    // Binding-Result-Tests ausgelassen ..
    service.update(id, data)
    return "redirect:/data/show";
}

DataService

@Transactional
public void update(int id, Data form) {
    Data data = dataDao.find(id);
    // Ok, ich habe die alten Werte in data und die neuen Werte in form -> teste Sachen ...
    // und manuell Felder von form nach data kopieren
    data.setName(form.getName);
    ...
}

Es funktioniert gut, aber im echten Fall, wenn ich viele Domänenobjekte und viele Felder in jedem habe, ist es ziemlich einfach, eins zu vergessen ... wenn Spring WebDataBinder es im Controller einschließlich Validierung erledigt hat, ohne dass ich etwas anderes als @ModelAttribute schreiben muss!

2/ Ich habe versucht, die Daten aus der Datenbank vorab zu laden, indem ich einen Konverter deklariere
DataConverter

public class DataConverter {
    Data convert(String strid) {
        return dataService.getId(Integer.valueOf(strid));
    }
}

Absolut magisch! Das data ist vollständig aus der Datenbank initialisiert und die in Form vorhandenen Felder werden ordnungsgemäß aktualisiert. Aber ... keine Möglichkeit, auf die vorherigen Werte zuzugreifen ...

Also ist meine Frage: Wie könnte der Weg sein, die Spring DataBinder-Magie und den Zugriff auf die vorherigen Werte meiner Domänenobjekte zu haben?

1voto

Martin Frey Punkte 9757

Sie haben bereits die möglichen Optionen gefunden, also werde ich hier nur einige Ideen hinzufügen ;)

Ich werde mit Ihrer Option beginnen, einen leeren Bean zu verwenden und die Werte in eine geladene Instanz zu kopieren:

Wie Sie in Ihrem Beispiel gezeigt haben, ist dies ein einfacher Ansatz. Es ist ziemlich einfach anpassbar, um eine generalisierte Lösung zu erstellen.

Sie müssen die Eigenschaften nicht manuell kopieren! Schauen Sie sich die Klasse 'BeanWrapperImpl' an. Dieses Spring-Objekt ermöglicht es Ihnen, Eigenschaften zu kopieren und wird tatsächlich von Spring selbst verwendet, um seine Magie zu erreichen. Es wird beispielsweise von den 'ParameterResolvers' verwendet.

Das Kopieren von Eigenschaften ist also der einfache Teil. Klone das geladene Objekt, fülle das geladene Objekt und vergleiche sie irgendwie.

Wenn Sie einen Service haben oder einfach nur mehrere, ist das der richtige Weg.

In meinem Fall benötigten wir diese Funktion auf jeder Entität. Bei der Verwendung von Hibernate haben wir das Problem, dass eine Entität nicht nur innerhalb eines bestimmten Service-Aufrufs, sondern theoretisch überall im System geändert werden kann.

Daher habe ich mich entschieden, eine 'MappedSuperClass' zu erstellen, von der alle Entitäten erben müssen. Diese Entität hat einen 'PostLoad'-Ereignislistener, der die Entität direkt nach dem Laden in einem transienten Feld klon. (Dies funktioniert, wenn Sie nicht Tausende von Entitäten in einer Anfrage laden müssen.) Dann benötigen Sie auch die 'PostPersist' und 'PostUpdate'-Listener, um den neuen Zustand erneut zu klonen, da Sie die Entität wahrscheinlich nicht erneut laden, bevor Sie eine weitere Änderung vornehmen.

Um das Controller-Mapping zu erleichtern, habe ich einen 'StringToEntityConverter' implementiert, der genau das tut, was Sie getan haben, jedoch verallgemeinert, um jeden Entitätstyp zu unterstützen.

Das Finden der Änderungen in einem generalisierten Ansatz erfordert ziemlich viel Reflexion. Es ist nicht so schwer und ich habe den Code gerade nicht verfügbar, aber Sie können auch den 'BeanWrapper' dafür verwenden:

Erstellen Sie einen Wrapper für beide Objekte. Holen Sie alle 'PropertyDescriptors' und vergleichen Sie die Ergebnisse. Der schwierigste Teil besteht darin herauszufinden, wann Sie aufhören sollen. Vergleichen Sie nur die erste Ebene oder benötigen Sie eine tiefe Vergleich?

Eine andere Lösung könnte auch sein, sich auf Hibernate Envers zu verlassen. Dies würde funktionieren, wenn Sie die Änderungen während derselben Transaktion nicht benötigen. Da Envers die Änderungen während eines Flushs verfolgt und eine 'Revision' erstellt, können Sie "einfach" zwei Revisionen abrufen und vergleichen.

In allen Szenarien müssen Sie einen Vergleichscode schreiben. Ich kenne keine Bibliothek, aber wahrscheinlich gibt es etwas in der Java-Welt :)

Hoffentlich hilft Ihnen das ein wenig.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X