Issue
I have a basic SpringBoot 2.0.4.RELEASE app. using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have a User object with roles:
@Entity
@Table(name="t_user")
public class User implements Serializable, UserDetails {
@ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
@JoinTable(
name="t_user_role",
joinColumns=@JoinColumn(name="user_id", referencedColumnName="id"),
inverseJoinColumns=@JoinColumn(name="role_id", referencedColumnName="id"))
private Set<Role> roles = new HashSet<>();
..
}
When I init the app. I create all the roles:
roleService.save(new Role(RolesEnum.USER.getRoleName()));
roleService.save(new Role(RolesEnum.ADMIN.getRoleName()));
Then I create a User with the USER role:
User user1 = new User();
Role role = roleService.findByName(RolesEnum.USER.getRoleName());
user.getRoles().add(role);
userService.save(user);
but When I create another user with the same role:
User user2 = new User();
Role role = roleService.findByName(RolesEnum.USER.getRoleName());
user2.getRoles().add(role);
user2Service.save(user);
I got this error:
Multiple representations of the same entity [com.tdk.backend.persistence.domain.backend.Role#1] are being merged. Detached: [com.tdk.backend.persistence.domain.backend.Role@5295d3de]; Detached: [com.tdk.backend.persistence.domain.backend.Role@2b3d9d32]
In the Role entity I don't have the field users declared since I will not get all the users based on a role
Solution
you are facing this issue because you are adding the object to the list/set which is already available.while performing save operation with oneToMany mapping
Now removing CascadeType.MERGE from cascade is one solution but not a best solution because after removing MERGE from Cascade you won't be able to update the mapped object ever
If you want to perform update operation also along with save for the mapped objects then before adding mapped object to list/collection just check/search within the list for the object and if the mapped object is available within the list then perform operation on that particular object.
keep cascade = CascadeType.ALL
Role role = roleService.findByName(RolesEnum.USER.getRoleName());
Note- make sure you have overridden hashcode/equals properly
boolean b = user2.getRoles().contains(role);
if (b!=true){
user2.getRoles().add(role);
}
user2Service.save(user);
or using stream
Role r= user2.getRoles().stream().filter(oldRole->oldRole.equals(role)).findAny().orElse(null);
if(r==null) {
user2.getRoles().add(role);
}
user2Service.save(user);
Answered By - abhinav kumar