JPA2/Hibernate: Many-to-Many-Relation wird u.a. beim löschen nicht aktualisiert

  • Themenstarter Themenstarter tuxedo
  • Beginndatum Beginndatum
T

tuxedo

Gast
Hallo zusammen,

ich hab ein kleines Hibernate/JPA Verständnis-Problem bei dem ich nicht weiter komme.

Ich hab folgende Entitäten:

Code:
EmUser:
-id
-name
-Set<EmUserGroup> groups <---------- MANY-TO-MANY

Code:
EmUserGroup:
-id
-name
-Set<EmUser> users <---------- MANY-TO-MANY - OWNER
-Set<EmPermission> permissions <---------- MANY-TO-MANY - OWNER

Code:
EmPermission:
-id
-name
-Set<EmUserGroup> groups <---------- MANY-TO-MANY


Alle Many-to-Many Beziehungen sind auf Eager-Loading eingestellt. Die Entität EmUserGroup ist der Owner der Many-to-Many Beziehungen. Die Cascade-Optionen habe ich nach bestem WIssen und Gewissen konfiguriert und schon verschiedene Konstellationen ausprobiert.

Folgendes Szenatio möchte ich unter anderem gerne realisieren:

Ich habe verschiedene User die in verschiedenen Gruppen sind. Wenn ich nun eine Gruppe lösche, dann sollen die User bestehen bleiben, aber die Gruppenzugehörigkeit zur gelöschten Gruppe soll verloren gehen.
Im Detail:

Mein EntityManager hat diverse User-Entitäten bereits geladen. Irgendwann später soll eine Gruppe gelöscht werden. Das löschen funktioniert.
Aber irgendwie schafft es der EntityManager nicht, die bereits geladenen User dahingehen zu aktualisieren, dass eine bestimmte Gruppe nun nicht mehr da ist und demnach die User-Group-Relation aktualisiert werden muss. Nach dem löschen ist in der Liste mit Gruppen der betroffenen User die gelöschte Gruppe noch vorhanden. Der EntityManager der die User geladen hat und die Gruppe gelöscht hat ist ein und derselbe. Warum geht das nicht?

Ich hab das ganze (Maven-)Projekt mal gezippt und angehängt. Ist einfach zu groß um die ganzen Klassen hier zu posten. Das ganze ist auf das notwendigste reduztiert und ist prinzipiell lauffähig. Wäre echt klasse wenn mir da jemand weiterhelfen kann.

Mein Gefühl sagt mir dass ich entweder

a) die Sache mit dem Cascade falsch mache

oder

b) das was ich vor habe von JPA2 gar nicht komplett abgedeckt wird und ich teilweise selbst Hand anlegen muss




Gruß
Alex

Ach ja: Ich hab keine Annotationen benutzt, da ich in der späteren Anwendung detachte Entitäten serialisieren/deserialisieren will und dort keine JPA-Abhängigkeit durch verwendung von Annotationen vorhanden sein soll. Auch handelt es sich NICHt um eine JEE Anwendung. Verwende Hibernate/JPA hier im Desktop-Umfeld.
 

Anhänge

Um die Frage noch etwas zu konkretisieren.

Das hier funktioniert nicht: Der User hat hinterher immer noch die Gruppe 2:

Java:
            EmUser foundUser = em.find(EmUser.class, 1l);
            
            System.out.println("found usergroup: "+foundUser.getGroups().toString());
            System.out.println("Removing Group id=2");
            em.getTransaction().begin();
            EmUserGroup groupToRemove = em.find(EmUserGroup.class, 2l);
            em.remove(groupToRemove);
            em.getTransaction().commit();
            System.out.println("after remove:    "+foundUser.getGroups().toString());

Wenn ich selbst hand anlege und die Relation aufbreche, dann geht's:

Java:
           EmUser foundUser = em.find(EmUser.class, 1l);
            
            System.out.println("found usergroup: "+foundUser.getGroups().toString());
            System.out.println("Removing Group id=2");
            em.getTransaction().begin();
            EmUserGroup groupToRemove = em.find(EmUserGroup.class, 2l);
            Set<EmUser> users = groupToRemove.getUsers();
            for (EmUser emUser : users) {
                emUser.getGroups().remove(groupToRemove);
                em.persist(emUser);
            }
            Set<EmPermission> permissions = groupToRemove.getPermissions();
            for (EmPermission emPermission : permissions) {
                emPermission.getGroups().remove(groupToRemove);
                em.persist(emPermission);
            }
            em.remove(groupToRemove);
            em.getTransaction().commit();
            System.out.println("after remove:    "+foundUser.getGroups().toString());

Die Frage ist: Muss ich wirklich selbst aktiv werden und die Relationen aufheben? Oder kann das Hibernate nicht für mich tun? Wenn ja: Wie?!
 
Hi,

mal ganz unabhängig von JPA und Cascaden, rein zumThema OO:
Es wäre imho keine gute Sache, dass man einfach so eine Gruppe löscht, und diese dann in allen Entitäten automatisch verschwindet, Kapselung und so.

Denke das der EM die Groupe wieder einfügt, weil er merkt dass diese noch referenziert wird, nachdem du versuchst sie explizit zu löschen, ohne die Gruppe aus EmUser und EmUserGroup zu entfernen.

Nach einem nur flüchtigen Blick in die Sourcen, fehlen mir die Möglichkeiten der "sauberen" Manipulation von EmUser und EmUserGroup, die reichen alle nur ihre Collections nach aussen.
addUser in der EmUserGroup und addUserGroup in EmUser wären imho Pflicht, natürlich mit den entsprechenden remove Methoden.
 
Zum Thema Kapselung: Ja, das ist mir bewusst. Da wird jedoch an anderer Stelle im eigentlichen Programm sorge getragen. Keine Angst ;-)

Denke das der EM die Groupe wieder einfügt, weil er merkt dass diese noch referenziert wird, nachdem du versuchst sie explizit zu löschen, ohne die Gruppe aus EmUser und EmUserGroup zu entfernen.

Naja, so wie ich das gesehen habe wird die Gruppe gelöscht, aber die Join-Table beinhaltet nach wie vor eine Referenz. Nur ist die halt "kaputt", da zur einen Seite keine Entity mehr da ist.

Nach einem nur flüchtigen Blick in die Sourcen, fehlen mir die Möglichkeiten der "sauberen" Manipulation von EmUser und EmUserGroup, die reichen alle nur ihre Collections nach aussen.
addUser in der EmUserGroup und addUserGroup in EmUser wären imho Pflicht, natürlich mit den entsprechenden remove Methoden.

Das ist wie gesagt nur ein Minimal-Beispiel. In der tatsächlichen Anwendung gibt es das alles.

Jetzt steh ich aber nach wie vor vor einer unbeantworteten Frage wie sich das mit dem fortpflanzen einer Änderung, die sich normalerweise auf eine Relation auswirkt, verhält...

Hab im Netz dazu diverses gelesen. Und als eine Lösung taucht da immer wieder auf, dass man sich selbst um das aufbrechen der Beziehung kümmern muss. Aber irgendwie ist das unschön. Zumal mir dann der Nutzen von JPA irgendwie nicht klar wird wenn ich mich auch noch um solche Dinge kümmern muss.

- Alex
 
Naja, so wie ich das gesehen habe wird die Gruppe gelöscht, aber die Join-Table beinhaltet nach wie vor eine Referenz. Nur ist die halt "kaputt", da zur einen Seite keine Entity mehr da ist.
Das dürfte gar nicht sein, wenn die DB eine RDBMS ist (MySQl mit MyISAM Tabellen ist zB. kein RDBMS 😉) und auf die contrainsts bez. des Foreign Keys achtet.

Aber wie gesagt, imho sollte der EM das lsöchen nicht wirklich durchführen.
 
geht das überhaupt, manyToMany mit eager loading? hab mal gelesen dass das nicht geht. kommt mir auch nicht sinnvoll vor (Zitat eines Freundes.. "da lädt man schnell mal die halbe Datenbak").
 
@maki

Ja, hab mich tatsächlich vertan. In der DB wird in der Join-Tabelle aufgeräumt, aber die bereits geladenen Entitäten von ein und demselben EM bekommen das nicht mit :-(

@JanHH

geht das überhaupt, manyToMany mit eager loading? hab mal gelesen dass das nicht geht.

Also wenn ich mich im Buch "Pro JPA2: Mastering the Java Persistence API" nicht verlesen habe, dann geht das sehr wohl.


kommt mir auch nicht sinnvoll vor (Zitat eines Freundes.. "da lädt man schnell mal die halbe Datenbak").

Dann sag deinem Freund 'nen schönen Gruß: Es gibt seht wohl Anwendungsfälle wo das Sinn macht und die Datenbank auch nicht so groß ist dass das zu einem Problem werden würde. Klar, im Allgemeinen ist davon abzuraten. Aber im Speziellen kann das durchaus Sinn machen. Denn wenn es zu 100.0% auszuschließen wäre dass das auch nur im entferntesten Sinn macht: Gäbe es diese Funktion dann? Wohl kaum. Aber in diesem Fall: Die Funktion gibt es, also kann und darf man sie, wenn man alle Warnungen dazu gelesen und verstanden hat, auch benutzen.
Aber muss ich mich jetzt rechtfertigen?

Bis dato hab ich nur zu lesen bekommen dass es es nicht tun sollte, das keine gute Idee wäre etc. Okay, Verstanden und zur Kenntnis genommen. Dennoch:

Keiner geht aber drauf ein ob ich hier von JPA tatsächlich eine Funktion erwarte die es nicht gibt... ???:L

- Alex
 
Hast du es mal mit cascade-remove probiert?

In der DB wird in der Join-Tabelle aufgeräumt, aber die bereits geladenen Entitäten von ein und demselben EM bekommen das nicht mit
Denke schon dass er es mitbekommt und aktiv verhindert.
 
Ja, hab ich. Dann entfernt er aber auch alle User wenn ich ne Gruppe lösche ... Nicht so der gewünschte Effekt.
 
Auch wenn das jetzt nicht zu der Lösung Beiträgt:

Wieso verzichtest du nicht auf den Cascade Mechanismus und machst es selber?

Schreib dir doch n Service der das (und andere dinge du die brauchst) macht.
 
Weiß denn hier keiner soweit Bescheid dass er klipp und klar sagen kann: Nein JPA kann das nicht es führt kein Weg am selber machen vorbei...Oder: Ja, JPA kann das. Such doch mal nach "xyz", dann weißt du mehr.

Dass ich's prinzipiell selber machen kann weiß ich selbst. Aber wieso selbst schreiben wenn's das schon gibt. [ironie]Ich mein, ich könnte ja auch JPA/Hibernate über Bord werfen und wieder das gute alte JDBC nutzen und alles selbst machen. Dann weiß ich wenigstens wie's und ob's funktioniert.[/ironie]
 
Zuletzt bearbeitet von einem Moderator:
@maki

Ja, hab mich tatsächlich vertan. In der DB wird in der Join-Tabelle aufgeräumt, aber die bereits geladenen Entitäten von ein und demselben EM bekommen das nicht mit

Passen die Transaktionen?

Hast du mal ein refresh() probiert?
 
Weiß denn hier keiner soweit Bescheid dass er klipp und klar sagen kann: Nein JPA kann das nicht es führt kein Weg am selber machen vorbei...Oder: Ja, JPA kann das. Such doch mal nach "xyz", dann weißt du mehr.
Offensichtlich nicht, ist ja auch etwas, dass man normalerweise niemals machen würde 😉

Dass ich's prinzipiell selber machen kann weiß ich selbst. Aber wieso selbst schreiben wenn's das schon gibt.
JPA bietet nicht für jeden obskuren Anwendungsfall eine implizite Lösung.

@raiL
Ohne entsprechende Cascade wird das nix, der EM kann ja schlecht Entitäten entfernen, die noch referenziert werden.
 
@maki

Offensichtlich nicht, ist ja auch etwas, dass man normalerweise niemals machen würde

Ja, das mag sein.

Ich hab nochmal gegoogelt und meine Bücher gewälzt. Über google lande ich immer wieder auf stackoverflow.com. Dort wird geschrieben, dass man entweder die ManyToMany Relation selbst aufheben, und dann die Entität löschen soll, oder aber als alternative die JoinTable doppelt bestrücken. Also nicth nur A auf B zeigen lassen, sondern auch B auf A. Dann hat man zwar für jede beziehung zwei Einträge in der Tabelle, aber JPA kriegt das mit dem auflösen der Beziehung von alleine hin. Allerdings scheint das mehr ein Hack als eine tolle Lösung zu sein.

Nachdem ich dann nochmal meine Bücher konsultiert habe, bin auch das hier gestoßen:

Pro JPA2: Mastering the Java Persitence API hat gesagt.:
Relationship maintenance is the responsibility of the application. We will repeat this statement
over the course of this book, but it cannot be emphasized enough. Almost every problem related to
removing an entity always comes back to this issue. If the entity to be removed is the target of foreign
keys in other tables, those foreign keys must be cleared for the remove to succeed. The remove
operation will either fail as it did here or it will result in stale data being left in the foreign key
columns referring to the removed entity in the event that there is no referential integrity.

In dem Umfeld habe ich im gleichen Buch auch zwischen den Zeilen lesen können, dass die Cascade-Geschichte wohl nicht der Standardfall ist. Überlicherweise managed man die Beziehungen selbst und hebt sie auch selbst wieder auf. Cascade benutzt man nur da, wo man sich sicher ist, dass es keine Kettenreaktion auf die halbe DB auslöst und man sich durch diese Annotation/Konfiguration ein wenig "arbeit" ersparen kann. Vor allem wenn an mehreren Stellen mit den Entitäten "gearbeitet" wird.

Mein persönliches Fazit lautet also:

Ich lass den Cascade-Krempel weitgehend weg und mach das in meiner UserManagerment Klasse selbst.

- Alex
 

Zurück
Oben