Reputation: 653
I started to work on a Spring Boot application using Spring Data JPA to setup a ManyToMany relationship between users and roles.
This relationship is defined as following in the User class:
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name="user_role", joinColumns = {@JoinColumn(name="user_id")}, inverseJoinColumns = {@JoinColumn(name="role_id")})
private Set<UserRole> roles;
I create roles using:
@Transactional
private void generateSeedRoles() {
UserRole adminRole = new UserRole(RoleEnum.ADMIN.toString());
userRoleRepository.save(adminRole);
UserRole userRole = new UserRole(RoleEnum.USER.toString());
userRoleRepository.save(userRole);
}
Assigning roles to users afterwards fails:
@Transactional
private void generateSeedUsers() {
UserRole adminRole = userRoleRepository.findUserRoleByRoleName("ADMIN");
User user = User.createUser("user1", "[email protected]", "pass");
user.setRoles(new HashSet<UserRole>(Arrays.asList(adminRole)));
userRepository.save(user);
}
The following exception is thrown (formatted for readability):
org.springframework.dao.InvalidDataAccessApiUsageException:
detached entity passed to persist: co.feeb.models.UserRole;
nested exception is org.hibernate.PersistentObjectException:
detached entity passed to persist: co.feeb.models.UserRole
However, if the user is saved before the relationship is created, it works fine:
@Transactional
private void generateSeedUsers() {
UserRole adminRole = userRoleRepository.findUserRoleByRoleName("ADMIN");
User user = User.createUser("user1", "[email protected]", "pass");
//Save user
userRepository.save(user);
//Build relationship and update user
user.setRoles(new HashSet<UserRole>(Arrays.asList(adminRole)));
userRepository.save(user);
}
Having to save/update the user twice seems somewhat unreasonable to me. Is there any way to assign the received role to the new user without saving the user first?
Upvotes: 5
Views: 12052
Reputation: 11643
When you save your entity, Spring internally checks if the entity is new (I forget the exact mechanism), and issues either a persist (if new) or merge (if existing).
Since you have defined your User to UserRole relationship with Cascade.ALL, any persists/merge from User will cascade to UserRole as well.
Given how Spring implements a save, and the behavior of cascading above, here are some scenarios to consider:
If you can guarantee that any UserRole you add to the relationship with User always exists, you could simply remove Cascade.Persist from your cascade options. Otherwise, I believe you'll have to use merge -- via custom repository and entityManager -- when saving your User in both scenarios above.
Regarding your use of Cascade.ALL, which in this situation probably doesn't make sense, since you've got a @ManyToMany relationship, cascading ALL from User to UserRole may have some undesired effects (e.g. Cascade.Remove would remove UserRole every time a User is removed).
Upvotes: 14