Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
459 views
in Technique[技术] by (71.8m points)

java - Spring data jpa detached entity

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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 both User and UserRole are new - you'll get a persist action when saving User, and a cascading persist to UserRole, which works fine since UserRole is new.
  • if User is new but UserRole is existing - again you'll get a persist action when saving User, and a cascading persist to UserRole, which results in an error since UserRole already exists!

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...