JPA(および対応する結合テーブルの行)でManyToMany関係を持つエンティティを削除するにはどうすればよいですか?


91

GroupとUserの2つのエンティティがあるとしましょう。すべてのユーザーは多くのグループのメンバーになることができ、すべてのグループは多くのユーザーを持つことができます。

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

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

グループを削除したいと思います(メンバーが多いとしましょう)。

問題は、あるグループでEntityManager.remove()を呼び出すと、JPAプロバイダー(私の場合はHibernate)が結合テーブルから行を削除せず、外部キーの制約のために削除操作が失敗することです。Userでremove()を呼び出すと、正常に機能します(これは、関係の所有側と関係があると思います)。

では、この場合、どうすればグループを削除できますか?

私が思いつく唯一の方法は、グループ内のすべてのユーザーをロードしてから、すべてのユーザーについて現在のグループをグループから削除し、ユーザーを更新することです。しかし、このグループを削除できるようにするためだけに、グループのすべてのユーザーに対してupdate()を呼び出すのはばかげているようです。

回答:


86
  • リレーションの所有権は、アノテーションの「mappedBy」属性を配置する場所によって決まります。'mappedBy'を配置したエンティティは、所有者ではありません。双方が所有者になる可能性はありません。「ユーザーの削除」のユースケースがない場合は、Group現在は所有者であるため、所有権をエンティティに移動するだけで済みますUser
  • 一方、あなたはそれについて質問していませんが、知っておく価値のあることが1つあります。groupsそしてusers互いに結合されていません。つまり、Group1.usersからUser1インスタンスを削除した後、User1.groupsコレクションは自動的に変更されません(これは私にとって非常に驚くべきことです)。
  • 全体として、所有者を決めることをお勧めします。Userが所有者であるとしましょう。その後、ユーザーを削除すると、リレーションuser-groupが自動的に更新されます。ただし、グループを削除するときは、次のように自分でリレーションを削除する必要があります。

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

Thnx!私は同じ問題を抱えていました、そしてあなたの解決策はそれを解決しました。しかし、この問題を解決する別の方法があるかどうかを知る必要があります。それは恐ろしいコードを生成します。em.remove(entity)にできないのはなぜですか?
Royi Freifeld 2013

2
これは舞台裏で最適化されていますか?データセット全体をクエリしたくありません。
Ced

1
これは本当に悪いアプローチです。そのグループに数千人のユーザーがいる場合はどうでしょうか。
Sergiy Sokolenko 2017

2
これはリレーションテーブルを更新しません。リレーションテーブルには、このリレーションに関する行が残ります。テストはしていませんが、問題が発生する可能性があります。
Saygın土偶

@SergiySokolenkoそのforループではデータベースクエリは実行されません。それらはすべて、トランザクション機能が終了した後にバッチ処理されます。
キルブ

42

以下は私のために働きます。リレーションシップの所有者ではないエンティティ(グループ)に次のメソッドを追加します

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

これが機能するためには、グループに更新されたユーザーのリストが必要であることに注意してください(これは自動的には行われません)。したがって、ユーザーエンティティのグループリストにグループを追加するたびに、グループエンティティのユーザーリストにもユーザーを追加する必要があります。


1
このソリューションを使用する場合は、cascade = CascadeType.ALLを設定しないでください。それ以外の場合は完璧に動作します!
ltsstar 2016年

@damianこんにちは私はあなたのソリューションを使用しましたが、concurrentModficationExceptionエラーが発生しました。あなたが指摘したように、理由はグループにユーザーの更新されたリストが必要である可能性があると思います。ユーザーに複数のグループを追加すると、この例外が発生するためです...
Adnan AbdulKhaliq19年

@Damianこれが機能するためには、グループに更新されたユーザーのリストが必要であることに注意してください(これは自動的には行われません)。したがって、ユーザーエンティティのグループリストにグループを追加するたびに、グループエンティティのユーザーリストにもユーザーを追加する必要があります。これについて詳しく説明していただけますか、例を挙げてください、thnks
Adnan

27

考えられる解決策を見つけましたが...それが良い解決策かどうかはわかりません。

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Role_id"),
            inverseJoinColumns=@JoinColumn(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",
            joinColumns=@JoinColumn(name="Permission_id"),
            inverseJoinColumns=@JoinColumn(name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

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

私はこれを試しましたが、うまくいきました。Roleを削除すると、リレーションも削除され(Permissionエンティティは削除されません)、Permissionを削除すると、Roleとのリレーションも削除されます(Roleインスタンスは削除されません)。ただし、単方向リレーションを2回マッピングしており、両方のエンティティがリレーションの所有者です。これにより、休止状態に問題が発生する可能性がありますか?どのタイプの問題ですか?

ありがとう!

上記のコードは、関連する別の投稿からのものです。


コレクションの更新の問題?
jelies 2010

12
これは正しくありません。双方向ManyToManyには、関係の所有者側が1つだけある必要があります。このソリューションは、双方を所有者として作成しており、最終的にはレコードが重複することになります。レコードの重複を避けるために、リストの代わりにセットを使用する必要があります。ただし、Setの使用は、推奨されていないことを行うための単なる回避策です。
L. Holandaの

4
では、そのような一般的なシナリオのベストプラクティスは何ですか?
SalutonMondo 2018

8

JPA / Hibernateソリューションの代替として:(Oracle構文)のように、結合テーブルの外部キーのデータベース定義でCASCADEDELETE句を使用できます。

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

これにより、グループを削除すると、DBMS自体がグループを指す行を自動的に削除します。また、削除がHibernate / JPA、JDBCから行われた場合でも、DBで手動で行われた場合でも、その他の方法で行われた場合でも機能します。

カスケード削除機能は、すべての主要なDBMS(Oracle、MySQL、SQL Server、PostgreSQL)でサポートされています。


3

その価値のために、私はEclipseLink 2.3.2.v20111125-r10461を使用しており、@ ManyToManyの一方向の関係がある場合は、あなたが説明している問題を観察します。ただし、双方向の@ManyToMany関係に変更すると、非所有側からエンティティを削除でき、JOINテーブルが適切に更新されます。これはすべて、カスケード属性を使用せずに行われます。


2

これは私のために働きます:

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

    // Other business logic

    groupRepository.delete(group);
}

また、メソッド@Transactional(org.springframework.transaction.annotation.Transactional)にマークを付けると、プロセス全体が1つのセッションで実行され、時間を節約できます。


1

これは良い解決策です。最良の部分はSQL側です–任意のレベルへの微調整は簡単です。

MySqlとMySqlWorkbenchを使用して、必須の外部キーの削除時にカスケードしました。

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

SOへようこそ。少し時間を取って、フォーマットと校正を改善するためにこれを調べてください:stackoverflow.com/help/how-to-ask
petezurich 2017

1

これが私がやったことです。うまくいけば、誰かがそれが役に立つと思うかもしれません。

@Transactional
public void deleteGroup(Long groupId) {
    Group group = groupRepository.findById(groupId).orElseThrow();
    group.getUsers().forEach(u -> u.getGroups().remove(group));
    userRepository.saveAll(group.getUsers());
    groupRepository.delete(group);
}

0

私の場合、mappedByを削除し、次のようにテーブルを結合しました。

@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;

3
多対多でcascadeType.allを使用しないでください。削除すると、Aレコードは関連するすべてのBレコードを削除します。そして、Bレコードは関連するすべてのAレコードを削除します。等々。ビッグノーノー。
ジョサイア

0

これは、参照が原因でユーザーを削除できなかった同様の問題で機能します。ありがとうございました

@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.