简体   繁体   中英

Getting Exception while adding new User in Spring Security

I have a user class and role class and user role class. Now every time i am trying to add a new user with a set of role which is already existing it throws a Unique error which is correct. But ideally it should not try to save the new role if it already exists. Below i am adding all my tables and save method.

@Table(name = "t_user")
@Data
public class User {

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

    @Column(name = "mobile_number")
    private String mobileNumber;

   
    @Column(name = "email")
    private String email;

   
    @Column(name = "first_name")
    private String firstName;

    @Size(max = 100)
    @NotBlank(message = "Last name can not be empty")
    @Column(name = "last_name")
    private String lastName;

    @Column(name = "is_archived")
    private Boolean isArchived = false;

    @Column(name = "qualification")
    private String qualification;

    @JsonIgnore
    @Column(name="password")
    private String password;

    @ManyToMany(cascade = CascadeType.ALL, 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<>();


}

@Data
@Table(name = "m_role")
@Entity
public class Role {

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

    @Column(name="name")
    private String name;

}

@Data
@Table(name = "t_user_role")
@Entity
public class UserRole {

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

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;
}

method where i am saving the user:

User newUser = new User();
        newUser.setFirstName(user.getFirstName());
        newUser.setLastName(user.getLastName());
        newUser.setEmail(user.getEmail());
        newUser.setMobileNumber(user.getPassword());
        newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
        newUser.setRoles(user.getRoles());
        return userRepository.save(newUser);
    }

and below is the post request format to create the user:

{
    "firstName":"first",
    "lastName":"name",
    "email":"email@gmail.com",
    "mobileNumber":"1110122223",
    "password":"1234567890",
    "roles":[{
        "name":"ADMIN"
    }]
}

I do not want to insert the role if present which should be ideal. But this is the standard way i find while implementing spring security with roles

Your request is missing id of the role. As id is not present. Spring try to add a new role in role table.

{
    "firstName":"first",
    "lastName":"name",
    "email":"email@gmail.com",
    "mobileNumber":"1110122223",
    "password":"1234567890",
    "roles":[{
        "id" : "" // ID must be present here.
        "name":"ADMIN"
    }]
}

Or from the role -> name , you can fetch Role entity/obj from the role table and set it in User object.

[Update 2]:

@ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
  @ToString.Exclude
  private Set<Role> roles = new HashSet<>();

You need to change the cascade type from 'ALL' to 'DETACH'. See: ALL means if you save USER, ROLE will also get saved, if you delete USER, role should also get delete. This is not what we want. You only need to use 'ROLE', not manipulate the 'ROLE' tables record in any way.

On behalf of what I understand, your requirements are:

  1. User entity
  2. Role entity, with each user having multiple roles
  3. If role is passed from client, you want to save the role only if it does not exist in your database, else you want to use the existing role (UPDATE: which as per comments and my opinion, is never an ideal thing to do)

In your case, I would suggest let Spring take care of the User->Roles relationship as follows:

public class User {

  ... all fields
  @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  @ToString.Exclude
  private Set<Role> roles = new HashSet<>();
}

public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
}

In Role repository, you would want a method: Optional<Role> findByName(String Name);

In between the layers (preferably in the service layer), try this:

public Map<String, Object> addUser(User user) {
    // perform validations

    user.getRoles().forEach(role -> {
        Role existing = roleRepository.findByName(role.getName())
                       .orElse(new Role(role.getName())); // using optional to create new role with name passed in from the client
        if (existing.getId() != null) user.setRole(existing); // UPDATE: NOT IDEAL
    });
    
    
    ... other tasks
    userRepository.save(user); // this all saves the correct role
    return yourResponseMap;
}

Other notes:

  1. We generally prefer to keep fetches Lazy, instead of Eager. But there are cases when you may need Eager retrieval so it depends on you.
  2. Letting Spring Data JPA handle third tables is better in terms of convenience in my opinion.
  3. org.hibernate.PersistentObjectException: detached entity passed to persist occurs when you're trying to save the role passed in from client directly without loading it on your application (see the service layer method for 'add user').
  4. Check this link , you might find it helpful.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM