0

I have a bidirectional many-to-many relationship between a Role and Scope. Creating both entities and even their childs with the help of CascadeType.PERSIST is easy and straightforward.

The Role entity is simples as that:

@Entity
@Table(uniqueConstraints = @UniqueConstraint(name = "role_name", columnNames = "name"))
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;
    
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "roles")
    private Set<Scope> scopes;

}

And the Scope:

@Entity
@Table(uniqueConstraints = @UniqueConstraint(name = "scope_name", columnNames = "name"))
public class Scope {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;

    @JoinTable(name = "role_scopes", joinColumns = @JoinColumn(name = "scope_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    @ManyToMany(cascade = CascadeType.REMOVE)
    private Set<Role> roles;

}

Their repositories are simply CrudRepository extensions:

public interface RoleRepository extends CrudRepository<Role, Long> {}
public interface ScopeRepository extends CrudRepository<Scope, Long> {}

The following snippet exemplifies the entities insertion:

Role adminRole = roleRepository.save(new Role("ADMIN"));
Scope allReadScope = scopeRepository.save(new Scope("all.read"));
Scope allWriteScope = scopeRepository.save(new Scope("all.write"));

Role and Scope can be both automatically easily persisted with the help of the CascadeType.PERSIST, as follows:

Role managedRole = roleRepository.save(new Role("ADMIN", new Scope("all.read"), new Scope("all.write")));

However... Updating managedRole leads to org.hibernate.PersistentObjectException: detached entity passed to persist exception:

managedRole.getScopes().remove(allReadScope);
roleRepository.save(managedRole); // PersistentObjectException!

I tried modifying the Role::scopes's CascadeType to also include DETACH, MERGE and/or REFRESH with no success. How do we get around this?

Yves Calaci
  • 1,019
  • 1
  • 11
  • 37

1 Answers1

0

Most likely you face the problem, because you don't maintain both sides of the relationship in the bidirectional mapping. Lets say in Role:

void add(Scope scope) {
   this.scopes.add(scope);
   scope.getRoles().add(this);
}

To be honest with you, I'd resign fully from bidirectional mapping. Maintaining this is a real nightmare.

jwpol
  • 1,188
  • 10
  • 22
  • It is actually maintained, I've just omitted it for brevity. I know it's a pain, but there's the only way I found to automatically create deletion cascade (upon `scope` deletion, also delete `role_scopes` if there's one). – Yves Calaci Jan 04 '21 at 04:13
  • Be careful with CascadeType.REMOVE in many-to-many -- by using it for ```scope``` you'll delete as you said ```scope```, ```role_scopes``` AND ```role```. – jwpol Jan 04 '21 at 10:25