How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?

Solution 1:

  • The ownership of the relation is determined by where you place the 'mappedBy' attribute to the annotation. The entity you put 'mappedBy' is the one which is NOT the owner. There's no chance for both sides to be owners. If you don't have a 'delete user' use-case you could simply move the ownership to the Group entity, as currently the User is the owner.
  • On the other hand, you haven't been asking about it, but one thing worth to know. The groups and users are not combined with each other. I mean, after deleting User1 instance from Group1.users, the User1.groups collections is not changed automatically (which is quite surprising for me),
  • All in all, I would suggest you decide who is the owner. Let say the User is the owner. Then when deleting a user the relation user-group will be updated automatically. But when deleting a group you have to take care of deleting the relation yourself like this:

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

Solution 2:

The following works for me. Add the following method to the entity that is not the owner of the relationship (Group)

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

Keep in mind that for this to work, the Group must have an updated list of Users (which is not done automatically). so everytime you add a Group to the group list in User entity, you should also add a User to the user list in the Group entity.

Solution 3:

I found a possible solution, but... I don't know if it's a good solution.

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

I have tried this and it works. When you delete Role, also the relations are deleted (but not the Permission entities) and when you delete Permission, the relations with Role are deleted too (but not the Role instance). But we are mapping a unidirectional relation two times and both entities are the owner of the relation. Could this cause some problems to Hibernate? Which type of problems?

Thanks!

The code above is from another post related.

Solution 4:

As an alternative to JPA/Hibernate solutions : you could use a CASCADE DELETE clause in the database definition of your foregin key on your join table, such as (Oracle syntax) :

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

That way the DBMS itself automatically deletes the row that points to the group when you delete the group. And it works whether the delete is made from Hibernate/JPA, JDBC, manually in the DB or any other way.

the cascade delete feature is supported by all major DBMS (Oracle, MySQL, SQL Server, PostgreSQL).

Solution 5:

For what its worth, I am using EclipseLink 2.3.2.v20111125-r10461 and if I have a @ManyToMany unidirectional relationship I observe the problem that you describe. However, if I change it to be a bi-directional @ManyToMany relationship I am able to delete an entity from the non-owning side and the JOIN table is updated appropriately. This is all without the use of any cascade attributes.