JPA Persistenz vom in GUI veränderten Daten-Modell

looparda

Top Contributor
Ich habe das Problem keinen geeigneten Ansatz zu finden Entities, die in einer JavaFX Applikation modifiziert werden richtig mit JPA abzubilden.
Problem hierbei ist, dass man 4-5 States in der Applikation hat:
1. aktuelle Einträge im View bspw. in einem ListView
2. aktuelle Werte in einem ViewModell
3. aktuelle Werte in einem Modell
(4. State im Persistence Context, falls attached oder detached)
5. Werte in der Datenbank
Für jeden Schichtwechsel benötigt man ein anderes Mapping. Bspw. darf man ein ListView nicht direkt mit der Collection eines Modells binden, falls man ein klassiches Fenstern mit Speichern und Abbrechen benutzt. Andernfalls würden Änderungen ohne, dass der User Speichern möchte ins Modell übertragen, selbst wenn Abbrechen gedrückt wird.
Außerdem müssen Beziehungen in den Collections beider Beziehungs-Enden synchron gehalten werden. Mit einem Binding der Oberfläche und dem Modell würde aber nur die Collection einer Entity die Änderung bekommen. Collections werden durch Hibernate durch eigene Collection-Typen ausgetauscht. Change-Listener auf Hibernate-Collections sind nicht implementiert) Mapping 1-2 erfolgt durch Binding. Für das Mapping 2-3 habe ich mir ein ListCompare überlegt (siehe Code).
Durch das Mapping entsteht viel Overhead und es ist fehleranfällig bei Änderungen.
Das ist nur ein Problem von vielen und ich verstehe nicht wie es so schwer sein kann eine Mini-Anwendung konsistent hinzubekommen. Vielleicht kann jemand open-source Projekte referenzieren, die JavaFX und JPA einsetzen, bei denen man sich etwas abschauen kann. Vielleicht ist meine Architektur auch einfach ungeeignet. Ich würde mich über Erfahrungen und Best Practice Ansätze freuen.

Ich habe aus meinem Projekt, bei dem die Probleme auftraten ein minimales Beispiel extrahiert, da es einfacher ist es zu überschauen und Sachen auszuprobieren:
https://github.com/No3x/javafx-springboot-boilerplate-hibernate-lifecycles-1nn1
 
Hast du 'ne ganz kurze Erklärung zu der Domäne? So rein von den Klassen kann ich zumindest aktuell noch nicht den Kontext ableiten, der wäre aber gut, um dazu was zu sagen
Das ist meistens eine sinnvolle Grundlage, um festzulegen, wer für dass aktualisieren einer Beziehung zuständig ist.



Zu dem Problem mit mehren States: ich würde vermutlich hart trennen zwischen dargestellten Daten und Entitys (in sowohl JPA- als auch DDD-Sinne).
Die gesamte Darstellungsschicht würde ich keine Referenzen auf die Entitys halten lassen, und stattdessen mit IDs arbeiten.
Alle anzuzeigenden Daten sind dann als Propertys im ViewModel vorhanden und können dort nach Lust und Laune verändert werden, und erst beim Speichern wird die Entity geladen, die Änderungen vorgenommen und gespeichert. Das Speichern löst dann ein Event aus, nach dem sich alle betroffenen View-Teile die neuen Daten holen.
 
Also ich hab das Mini-Projekt ja extrahiert aus meinem eigentlichen Projekt und dachte, es wäre wegen der Anzahl der Entities überschaubar. Es geht in dem Beispiel nur darum Personen und Teams zu verwalten. Personen können mehreren Teams zugeordnet werden, Teams haben mehrere Personen. Da ich in meinem eigentlichen Projekt unbedingt Attribute in der "Auflösungs-Entität" (hier: PersonTeam) benötige habe ich hier die Attribute Datum und einen String "createdBy" dazugenommen. Eine bestimmte Aufgabe hat die gesamte Applikation nicht, außer mein Realsystem möglichst abzubilden von Persistenzschicht bis hoch zur GUI. Viele Probleme treten eben erst mit Verwendung einer GUI au
Alle anzuzeigenden Daten sind dann als Propertys im ViewModel vorhanden
Also würdest du im ViewModel dein Model auseinander nehmen und in Typen wie String, Integer, Date usw. mappen?
Die gesamte Darstellungsschicht würde ich keine Referenzen auf die Entitys halten lassen, und stattdessen mit IDs arbeiten.
Kannst du hier genauer darauf eingehen wie das aussehen würde?
 
Also ich hab das Mini-Projekt ja extrahiert aus meinem eigentlichen Projekt und dachte, es wäre wegen der Anzahl der Entities überschaubar.
Technisch schon, aber um irgendwas zu der Struktur zu sagen, ist's meist sinnvoll, von weiter oben darauf zu gucken 😉

Es geht in dem Beispiel nur darum Personen und Teams zu verwalten. Personen können mehreren Teams zugeordnet werden, Teams haben mehrere Personen. Da ich in meinem eigentlichen Projekt unbedingt Attribute in der "Auflösungs-Entität" (hier: PersonTeam) benötige habe ich hier die Attribute Datum und einen String "createdBy" dazugenommen.
Im wesentlichen; es gibt Personen, Teams und Mitgliedschaften?

Ich persönlich würde das mit möglichst nur uni-direktionalen Beziehungen modellieren.
d.h, Personen kennen die Mitgliedschaften nicht direkt (und wenn, nur lesend), genauso Teams, aber Mitgliedschaften kennen Team und Person. Das nimmt meist einen Haufen Probleme bei der Modellierung weg und macht die Nutzung einfacher.

Also würdest du im ViewModel dein Model auseinander nehmen und in Typen wie String, Integer, Date usw. mappen?
Jein, nicht aus die Typen direkt, sonder aus den jeweiligen JavaFX-Propertys.

Kannst du hier genauer darauf eingehen wie das aussehen würde?
Im wesentlichen einfach ein finales Attribut mit dem Typ der ID der jeweiligen Entity, was am besten im Konstruktor direkt zugewiesen wird.
Und dann wie oben gesagt für alle relevanten Felder eine entsprechende Property.

Wenn dann gespeichert wird, wird mit der ID die Entity geladen, die Änderungen (die dann zT einfach die Werte der Properties sind) an der durchgeführt und dann direkt wieder gespeichert.
 
Im wesentlichen einfach ein finales Attribut mit dem Typ der ID der jeweiligen Entity, was am besten im Konstruktor direkt zugewiesen wird.
In welchem Konstruktor? - eine neue extra Model-Klasse pro Entity für die GUI?

Wie geh ich mit Collections um? An einer Stelle muss ich dann ja wieder die Unterscheidung machen ob ein Eintrag in die Datenbank eingefügt, bearbeitet oder gelöscht werden soll.
 
Zuletzt bearbeitet:
In welchem Konstruktor? - eine neue extra Model-Klasse pro Entity für die GUI?
Das ViewModel eben 😉

Wobei, um bei deinem Beispiel zu blieben: PersonDetailScope würde keine ObjectProperty<Person> mehr haben, sondern eine LongProperty. Die enthält dann statt der Person nur die ID der Person.
(Wobei beide Varianten Vor und Nachteile haben...)
 
Das geht vermutlich super bei Entities ohne Collection. Andernfalls läuft es vermutlich darauf hinaus, dass man seine Collection in eine ObservableList<String> mappt. In meinem Beispiel in der GUI habe ich es so, dass sich der rechte ListView in Abhängigkeit des linken ListView aktualisiert. Wenn man eine anderen Person auswählt werden die Teams der Person angezeigt. Dabei gehe ich über das aktuell selektierte Element des linken ListView. Außerdem wird das selektierte Element im PersonDetailScope gespeichert.
Dabei sehe ich das Problem: Du schlägst vor die Id im PersonDetailScope zu speichern und meine Entity in Properties zu mappen. Woher bekomme ich jedoch wieder die id, wenn ich über die GUI nur noch an das aktuell ausgewählte Element der ListView als String komme?
 
Das geht vermutlich super bei Entities ohne Collection. Andernfalls läuft es vermutlich darauf hinaus, dass man seine Collection in eine ObservableList<String> mappt.
Oder einfach eine ObservableList<PassendesViewModel> 😉


In meinem Beispiel in der GUI habe ich es so, dass sich der rechte ListView in Abhängigkeit des linken ListView aktualisiert. Wenn man eine anderen Person auswählt werden die Teams der Person angezeigt. Dabei gehe ich über das aktuell selektierte Element des linken ListView. Außerdem wird das selektierte Element im PersonDetailScope gespeichert.
Dabei sehe ich das Problem: Du schlägst vor die Id im PersonDetailScope zu speichern und meine Entity in Properties zu mappen. Woher bekomme ich jedoch wieder die id, wenn ich über die GUI nur noch an das aktuell ausgewählte Element der ListView als String komme?
Naja, du musst ja nicht Strings als Elemente nehmen, du kannst ja immer noch Klassen nehmen, die dann auch die ID enthalten. Ich würde nur Trennen von Objekten für die Darstellung und Entitys.
 

Zurück
Oben