如何在JPA中删除具有ManyToMany关系的实体(以及相应的联接表行)?


91

假设我有两个实体:“组”和“用户”。每个用户可以是多个组的成员,每个组可以有多个用户。

@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()只是为了能够删除该组,这似乎很荒谬。

Answers:


86
  • 关系的所有权取决于将“ mappedBy”属性放置在注释中的位置。您放置“ mappedBy”的实体不是所有者。双方都没有机会成为所有者。如果您没有“删除用户”用例,则可以简单地将所有权移至Group实体,因为当前User是所有者。
  • 另一方面,您没有问过这个问题,但是有一件值得知道的事情。的groupsusers不与彼此组合。我的意思是,从Group1.users中删除User1实例后,User1.groups集合不会自动更改(这对我来说很令人惊讶),
  • 总而言之,我建议您决定谁是所有者。假设User是所有者。然后,在删除用户时,关系用户组将自动更新。但是,在删除组时,您必须像这样删除自己的关系:

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

谢谢!我遇到了同样的问题,您的解决方案也解决了。但是我必须知道是否还有另一种方法可以解决此问题。它产生一个可怕的代码。为什么不能是em.remove(entity)就是这样?
罗伊·弗赖菲尔德

2
这在幕后进行了优化吗?因为我不想查询整个数据集。
Ced

1
这确实是一种糟糕的方法,如果您在该组中有成千上万的用户,该怎么办?
Sergiy Sokolenko

2
这不会更新关系表,在关系表中仍保留有关该关系的行。我没有测试,但这可能会导致问题。
Saygın多乌

@SergiySokolenko在该for循环中不执行数据库查询。保留事务功能后,将全部批处理它们。
Kilves

42

以下对我有用。将以下方法添加到不是关系(组)所有者的实体

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

请记住,要使此功能起作用,组必须具有更新的用户列表(不会自动完成)。因此,每次将“网上论坛”添加到“用户”实体中的群组列表时,也应将“用户”添加到“群组”实体中的用户列表。


1
当您要使用此解决方案时,不应设置cascade = CascadeType.ALL。否则它会完美!
ltsstar '16

@damian嗨,我已经使用了您的解决方案,但是我遇到了一个错误parallelModficationException,我想正如您所指出的,原因可能是该组必须具有更新的用户列表。因为如果我向用户添加多个组,则会出现此异常...
Adnan Abdul Khaliq

@Damian请记住,要使此功能起作用,组必须具有更新的用户列表(不会自动完成)。因此,每次将“网上论坛”添加到“用户”实体中的群组列表时,也应将“用户”添加到“群组”实体中的用户列表。您能否详细说明一下,举个例子,thnks
Adnan Abdul Khaliq,

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

我已经尝试过了,并且有效。删除角色时,关系也将被删除(但不删除权限实体),删除权限时,与角色的关系也将被删除(但不删除角色实例)。但是我们两次映射单向关系,并且两个实体都是该关系的所有者。这会导致一些问题休眠吗?哪种类型的问题?

谢谢!

上面的代码来自另一个相关的帖子


集合令人耳目一新的问题?
jelies

12
这是不正确的。双向ManyToMany应该只有一个关系的所有者一方。该解决方案使双方都成为所有者,最终将导致重复的记录。为了避免重复的记录,必须使用集合而不是列表。但是,使用Set只是解决不建议做的事情的一种解决方法。
L. Holanda

4
那么对于这种常见的场景,最佳实践是什么?
SalutonMondo

8

作为JPA / Hibernate解决方案的替代方案:您可以在联接表的前键的数据库定义中使用CASCADE DELETE子句,例如(Oracle语法):

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

这样,DBMS本身会在删除组时自动删除指向该组的行。无论是通过Hibernate / JPA,JDBC,在数据库中手动进行删除还是通过其他任何方式进行删除,它都可以正常工作。

所有主要的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

这是一个很好的解决方案。最好的部分是在SQL方面–轻松调整到任何级别都很容易。

我使用MySql和MySql Workbench在删除所需外键时进行级联。

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

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})
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.