wake-up-neo.com

Wie entferne ich eine Entität mit der ManyToMany-Beziehung in JPA (und entsprechenden Join-Tabellenzeilen)?

Nehmen wir an, ich habe zwei Entitäten: Gruppe und Benutzer. Jeder Benutzer kann Mitglied in vielen Gruppen sein und jede Gruppe kann viele Benutzer haben.

@Entity
public class User {
    @ManyToMany
    Set<Group> groups;
    //...
}

@Entity
public class Group {
    @ManyToMany(mappedBy="groups")
    Set<User> users;
    //...
}

Jetzt möchte ich eine Gruppe entfernen (sagen wir, es hat viele Mitglieder).

Problem ist, dass beim Aufruf von EntityManager.remove () in einer Gruppe der JPA-Provider (in meinem Fall Hibernate) keine Zeilen aus der Join-Tabelle entfernt und der Löschvorgang aufgrund von Fremdschlüsseleinschränkungen fehlschlägt. Das Aufrufen von remove () auf User funktioniert einwandfrei (ich denke, das hat etwas mit der eigenen Beziehungsseite zu tun).

Wie kann ich in diesem Fall eine Gruppe entfernen? 

Die einzige Möglichkeit, die ich mir vorstellen kann, ist, alle Benutzer in der Gruppe zu laden, dann für jeden Benutzer die aktuelle Gruppe aus seinen Gruppen zu entfernen und den Benutzer zu aktualisieren. Es erscheint mir jedoch lächerlich, update () für jeden Benutzer aus der Gruppe aufzurufen, nur um diese Gruppe löschen zu können.

74
rdk
  • Der Besitz der Beziehung wird dadurch bestimmt, wo Sie das Attribut 'mappedBy' in die Annotation einfügen. Die Entität, die Sie "MappedBy" eingeben, ist diejenige, die NICHT der Eigentümer ist. Für beide Seiten besteht keine Chance, Eigentümer zu sein. Wenn Sie keinen Anwendungsfall "Benutzer löschen" haben, können Sie den Besitz einfach in die Group-Entität verschieben, da derzeit User der Eigentümer ist.
  • Auf der anderen Seite haben Sie nicht danach gefragt, aber eine Sache, die es zu wissen lohnt. groups und users werden nicht miteinander kombiniert. Ich meine, nach dem Löschen der User1-Instanz aus Group1.users werden die User1.groups-Sammlungen nicht automatisch geändert (was für mich ziemlich überraschend ist).
  • Alles in allem würde ich vorschlagen, dass Sie entscheiden, wer der Eigentümer ist. Nehmen wir an, die User ist der Besitzer. Wenn Sie einen Benutzer löschen, wird die Beziehungsgruppe automatisch aktualisiert. Wenn Sie jedoch eine Gruppe löschen, müssen Sie die Beziehung selbst wie folgt löschen:

entityManager.remove(group)
for (User user : group.users) {
     user.groups.remove(group);
}
...
// then merge() and flush()
70

Folgendes funktioniert für mich. Fügen Sie der Entität, die nicht Eigentümer der Beziehung ist, die folgende Methode hinzu (Gruppe).

@PreRemove
private void removeGroupsFromUsers() {
    for (User u : users) {
        u.getGroups().remove(this);
    }
}

Beachten Sie, dass die Gruppe über eine aktualisierte Liste von Benutzern verfügen muss (dies erfolgt nicht automatisch). Jedes Mal, wenn Sie der Gruppenliste in Benutzerentität eine Gruppe hinzufügen, sollten Sie auch einen Benutzer zur Benutzerliste in der Gruppenentität hinzufügen.

36
Damian

Ich habe eine mögliche Lösung gefunden, aber ... ich weiß nicht, ob es eine gute Lösung ist.

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            [email protected](name="Role_id"),
            [email protected](name="Permission_id")
        )
    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

@Entity
public class Permission extends Identifiable {

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            [email protected](name="Permission_id"),
            [email protected](name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

Ich habe es versucht und es funktioniert. Wenn Sie Rolle löschen, werden auch die Beziehungen gelöscht (nicht jedoch die Berechtigungsentitäten), und wenn Sie Berechtigung löschen, werden auch die Beziehungen mit Rolle gelöscht (nicht jedoch die Rolleninstanz). Wir bilden jedoch zweimal eine unidirektionale Beziehung ab und beide Entitäten sind der Eigentümer der Beziehung. Könnte dies zu Problemen beim Überwintern führen? Welche Art von Problemen?

Vielen Dank!

Der obige Code stammt von einem anderen post .

24
jelies

Alternativ zu JPA/Hibernate-Lösungen: Sie können eine CASCADE DELETE-Klausel in der Datenbankdefinition Ihres Foregin-Schlüssels für Ihre Join-Tabelle verwenden, z. B. (Oracle-Syntax):

CONSTRAINT fk_to_group
     FOREIGN KEY (group_id)
     REFERENCES group (id)
     ON DELETE CASCADE

Auf diese Weise löscht das DBMS automatisch die Zeile, die auf die Gruppe zeigt, wenn Sie die Gruppe löschen. Und es funktioniert, ob das Löschen von Hibernate/JPA, JDBC, manuell in der DB oder auf andere Weise vorgenommen wird.

die Kaskadenlöschfunktion wird von allen gängigen DBMS (Oracle, MySQL, SQL Server, PostgreSQL) unterstützt.

7
Pierre Henry

Ich nutze EclipseLink 2.3.2.v20111125-r10461 und wenn ich eine unidirektionale @ManyToMany-Beziehung habe, beobachte ich das Problem, das Sie beschreiben. Wenn ich es jedoch in eine bidirektionale @ManyToMany-Beziehung umwandle, kann ich eine Entität von der Nicht-Besitzerseite löschen und die JOIN-Tabelle wird entsprechend aktualisiert. Dies ist alles ohne die Verwendung von Kaskadenattributen.

3
NBW

Dies ist eine gute Lösung. Das Beste ist auf der SQL-Seite - die Feinabstimmung auf jeder Ebene ist einfach.

Ich habe MySql und MySql Workbench zum Löschen für den erforderlichen Fremdschlüssel verwendet.

ALTER TABLE schema.joined_table 
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;
1
user1776955

Für meinen Fall habe ich mappedBy gelöscht und Tabellen wie folgt zusammengefügt: 

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "user_group", joinColumns = {
        @JoinColumn(name = "user", referencedColumnName = "user_id")
}, inverseJoinColumns = {
        @JoinColumn(name = "group", referencedColumnName = "group_id")
})
private List<User> users;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JsonIgnore
private List<Group> groups;
1
szachMati

Das funktioniert für mich:

@Transactional
public void remove(Integer groupId) {
    Group group = groupRepository.findOne(groupId);
    group.getUsers().removeAll(group.getUsers());

    // Other business logic

    groupRepository.delete(group);
}

Markieren Sie außerdem die Methode @Transactional (org.springframework.transaction.annotation.Transactional). Dadurch wird der gesamte Vorgang in einer Sitzung ausgeführt, was einige Zeit spart.

0
Mehul Katpara