首页 文章

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

提问于
浏览
72

假设我有两个实体:Group和User . 每个用户都可以是许多组的成员,每个组可以拥有许多用户 .

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

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

现在我想删除一个组(假设它有很多成员) .

问题是当我在某些Group上调用EntityManager.remove()时,JPA提供程序(在我的情况下是Hibernate) does not remove rows from join table 并且由于外键约束而删除操作失败 . 在User上调用remove()工作正常(我猜这与拥有关系的一方有关) .

那么在这种情况下如何删除组呢?

我能想到的唯一方法是加载组中的所有用户,然后为每个用户从他的组中删除当前组并更新用户 . 但是,为了能够删除该组,对组中的每个用户调用update()似乎很荒谬 .

7 回答

  • 1

    这是一个很好的解决方案 . 最好的部分是在SQL方面 - 对任何级别的微调很容易 .

    我使用MySql和MySql Workbench来删除所需的外键 .

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

    这对我有用:

    @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),这将在一个会话中完成整个过程,节省一些时间 .

  • 22
    • 关系的所有权取决于将'mappedBy'属性放置到注释的位置 . 您放置'mappedBy'的实体是非所有者的实体 . 有一个'delete user'用例你可以简单地将所有权移动到 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()
    
  • 67

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

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

    请记住,要使其工作,该组必须具有更新的用户列表(不会自动完成) . 因此,每次将组添加到用户实体中的组列表时,还应将用户添加到组实体中的用户列表 .

  • 3

    我找到了一个可能的解决方案,但是......我不知道这是不是一个好的解决方案 .

    @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实例) . 但是我们两次映射单向关系,两个实体都是关系的所有者 . 这会给Hibernate带来一些问题吗?哪种类型的问题?

    谢谢!

    上面的代码来自另一个post相关 .

  • 6

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

    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)都支持级联删除功能 .

  • 0

    为了它的 Value ,我使用EclipseLink 2.3.2.v20111125-r10461,如果我有@ManyToMany单向关系,我会观察你描述的问题 . 但是,如果我将其更改为双向@ManyToMany关系,我可以从非拥有方删除实体,并且JOIN表会相应更新 . 这都不使用任何级联属性 .

相关问题