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).