java – Problems removing @ManyToMany element with @JoinTable

Question:

I'm having trouble deleting a @ManyToMany relationship in JPA. In summary, I have 3 tables: USER, PERMISSAO and USUARIO_PERMISSAO which is the relationship of the previous ones, the relationship is N to N. The problem is that I'm not sure how to remove or add a new permission to a user that already exists in the base, follows a simplified model:

@Entity
@Table(name="TB_USUARIO")
public class Usuario {

    @Id
    @Column(name="NO_USUARIO", unique=true, nullable=false, length=50)
    private String noUsuario;

    @ManyToMany
    @JoinTable(
        name="TB_USUARIO_PERMISSAO"
        , joinColumns={
            @JoinColumn(name="NO_USUARIO", nullable=false)
            }
        , inverseJoinColumns={
            @JoinColumn(name="NO_METODO", referencedColumnName="NO_METODO", nullable=false),
            @JoinColumn(name="NO_PERMISSAO", referencedColumnName="NO_PERMISSAO", nullable=false))
            }
        )
    private List<Permissao> permissoes;
}



@Entity
@Table(name="TB_PERMISSAO")
public class Permissao {

    @EmbeddedId
    private PermissaoPK id;

    @ManyToMany(mappedBy="permissoes")
    private List<Usuario> usuarios;
}

My problem is with the mapping of the permissions field of the User class, when I insert a new User in the database, adding the permission works, following code that works:

Usuario usuario = new Usuario("USUARIO_01");
usuario.getPermissoes().add(permissao); //permissao ja cadastrada no banco
entityManager.persist(usuario);

This code correctly inserts a new user and also the relationship with the permission in the TB_USUARIO_PERMISSAO table. The problem now is to delete the permissions, let's suppose that a user has 2 permissions and I want to delete one of them, how do I do that? Here's an example that doesn't work :

Usuario usuario = entityManager.find(Usuario.class, "USUARIO_01");
usuario.getPermissoes().remove(0);
entityManager.merge(usuario);

This code above doesn't work, do I have to change the mapping, I don't know, maybe creating a UsuarioPermissao entity? The mapping is the way the reverse engineering engine created it. If I remove a user, the permissions are automatically deleted. My problem is to delete and also add a new permission of a user already registered in the bank. I've also used Cascade and to no avail.

Can you help?

Note: for simplicity I changed the code by the editor, there may be some error that if you put it in the IDE it won't compile, the idea is to show the base. I also tried other ways to remove/add that I thought it was better not to mention them as they didn't work.

Grateful!

Answer:

First of all I believe your mapping can be simplified, especially the join table.

To centralize the mapping of the entities' primary key, I created a BaseEntity , like this:

@MappedSuperclass
public abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

}

The Usuario entity was like this:

@Entity
@Table(name = "tb_usuario")
public class Usuario extends BaseEntity {

    @Column(name = "no_usuario", unique = true, nullable = false, length = 50)
    private String nome;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) 
    @JoinTable(name = "tb_usuario_permissao", joinColumns = {
                @JoinColumn(name = "id_usuario", nullable = false)
            }, inverseJoinColumns = {
                @JoinColumn(name = "id_permissao", referencedColumnName = "id", nullable = false)
            }
    )
    private Set<Permissao> permissoes;

}

Here we score the following:

  • don't mix business logic with your data model, so don't use the username as the primary key, consider it a unique key and have
  • cascade include: I don't know if you'll need this, it just includes so you don't need to persist each permission separately. For more details on the behavior of each type see the JPA specification
  • since we have a primary key in tb_permissao it doesn't make sense to have two join columns that reference business columns. There is usually more than one join column when it is a foreign key to another table, for example.

The Permissao entity looked like this:

@Entity
@Table(name = "tb_permissao")
public class Permissao extends BaseEntity {

    @Column(name = "no_permissao", unique = true, nullable = false, length = 50)
    private String nome;

    @Column(name = "no_metodo", unique = true, nullable = false, length = 50)
    private String metodo;

}

Unless you really need to know all users have a certain permission you don't need to map, even in this case I would prefer to do a query.

Now, the removal part, let's look at this excerpt:

usuario.getPermissoes().remove(0);

Well, with this model above the removal occurred as desired, logically when there is one or more permissions.

as an example (I'm using lombok ), the insertion test looked like this:

final Permissao permissao1 = Permissao.builder().nome("permissao1").metodo("metodo1").build();
em.persist(permissao1);

final Permissao permissao2 = Permissao.builder().nome("permissao2").metodo("metodo2").build();
em.persist(permissao2);

final Usuario usuario = Usuario.builder().nome("Bruno").permissao(permissao1).permissao(permissao2).build();
em.persist(usuario);

And so:

final Permissao permissao3 = Permissao.builder().nome("permissao3").metodo("metodo3").build();
final Permissao permissao4 = Permissao.builder().nome("permissao4").metodo("metodo4").build();

final Usuario usuario = Usuario.builder().nome("César").permissao(permissao3).permissao(permissao4).build();
em.persist(usuario);

Removal like this:

final Usuario usuario = em.find(Usuario.class, 1L);
usuario.getPermissoes().remove(0);
em.merge(usuario);

And adding a new permission to an existing user like this:

final Permissao permissao = em.find(Permissao.class, 1L);
final Usuario usuario = em.find(Usuario.class, 1L);
usuario.getPermissoes().add(permissao);
em.merge(usuario);

And from a non-existent one like this:

final Permissao permissao5 = Permissao.builder().nome("permissao5").metodo("metodo5").build();
final Usuario usuario = em.find(Usuario.class, 1L);
usuario.getPermissoes().add(permissao5);
em.merge(usuario);

As you didn't provide further details in your question, if you don't solve your problem, I ask you to provide more details, such as your PermissaoPK (to find out if you really need it).

Scroll to Top