web-dev-qa-db-ja.com

JPA(および対応する結合テーブル行)でManyToMany関係を持つエンティティを削除する方法は?

グループとユーザーの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()を呼び出すのはばかげているようです。

78
rdk
  • リレーションの所有権は、注釈に「mappedBy」属性を配置する場所によって決まります。 「mappedBy」と入力したエンティティは、所有者ではありません。双方が所有者になる機会はありません。 「ユーザーの削除」のユースケースがない場合、現在Groupが所有者であるため、所有権をUserエンティティに単純に移動できます。
  • 一方、あなたはそれについて尋ねていませんでしたが、知っておく価値のあることの1つです。 groupsusersは互いに結合されていません。つまり、Group1.usersからUser1インスタンスを削除しても、User1.groupsコレクションは自動的に変更されません(これは非常に驚くべきことです)。
  • 全体として、所有者を決定することをお勧めします。 Userが所有者であるとしましょう。その後、ユーザーを削除すると、関係ユーザーグループが自動的に更新されます。ただし、グループを削除するときは、次のようにリレーションを自分で削除する必要があります。

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

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

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

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

38
damian

可能な解決策を見つけましたが、...それが良い解決策かどうかはわかりません。

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

私はこれを試しましたが、うまくいきます。ロールを削除すると、関係も削除されますが(Permissionエンティティは削除されません)、Permissionを削除すると、Roleとの関係も削除されます(Roleインスタンスは削除されません)。しかし、一方向の関係を2回マッピングしており、両方のエンティティが関係の所有者です。これはHibernateにいくつかの問題を引き起こす可能性がありますか?どのタイプの問題ですか?

ありがとう!

上記のコードは、別の post 関連のものです。

26
jelies

JPA/Hibernateソリューションの代替として:(Oracle構文)など、結合テーブルのforeginキーのデータベース定義でCASCADE DELETE句を使用できます:

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)でサポートされています。

7
Pierre Henry

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

3
NBW

これは良い解決策です。最良の部分はSQL側です。あらゆるレベルへの微調整は簡単です。

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

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

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

@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

これは私のために働く:

@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つのセッションでプロセス全体が実行され、時間が節約されます。

0
Mehul Katpara