简体   繁体   中英

Unable to save entity using thymeleaf and Spring Boot

I've recently started using Spring Boot (I mostly come from a python/flask and node background) with JPA and thymeleaf and I'm trying to create a Project in my database. Everything was going well, I was able to add, delete, etc.. Projects.

I added a variable Set users to my Project entity which looks like this:

@Entity
@Table(name = "project")
public class Project {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "project_id")
private int id;

@Column(name = "title")
@NotEmpty(message = "*Please provide a title")
private String title;

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

@OneToOne(cascade = CascadeType.ALL)
@JoinTable(name = "project_lead", joinColumns = @JoinColumn(name = "project_id"), inverseJoinColumns = @JoinColumn(name = "user_id"))
private User projectLead;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "project_user", joinColumns = @JoinColumn(name = "project_id"), inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<User> users;
...
}

The user class looks like this:

@Entity
@Table(name = "user")
public class User {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "user_id")
private int id;

@Column(name = "email")
@Email(message = "*Please provide a valid Email")
@NotEmpty(message = "*Please provide an email")
private String email;

@Column(name = "username")
@NotEmpty(message = "*Please provide a username")
private String username;

@Column(name = "password")
@Length(min = 5, message = "*Your password must have at least 5 characters")
@NotEmpty(message = "*Please provide your password")
@Transient
private String password;

@Column(name = "enabled")
private int enabled;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles;
...
}

I'm able to create new projects when I don't specify the users for the project. But I'm having trouble creating a project when I specify the users using a multiple select. In my form I have:

<form th:action="@{/secure/projects}" th:object="${project}" method="POST">
        <div class="form-group">
            <label th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="validation-message"></label>
            <input type="text" class="form-control" th:field="*{title}" id="title" th:placeholder="Title" /> 
        </div>

        <div class="form-group">
            <input type="text" class="form-control" th:field="*{description}" id="description" th:placeholder="Description" /> 
        </div>

        <div class="form-group">
            <select th:field="*{users}" class="users-select form-control" multiple="multiple">
              <option th:each="user : ${allUsers}" th:value="${user}" th:text="${user.username}"></option>
            </select>
        </div>


        <button name="Submit" value="Submit" type="Submit" th:text="Create"></button>
   </form>

I keep getting 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users'' for the th:field="*{users}". I don't understand why th:value="${user}" is being considered as a String when it's supposed to be of class User. Is there a way for me to simply get the results of the select, loop through it and manually add it in the controller to my object project?

My controller looks like this:

@RequestMapping(value = "/projects", method=RequestMethod.GET)
public ModelAndView showProjectForm() {
    ModelAndView modelAndView = new ModelAndView();
    // Get authenticated user
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    User user = userService.findByEmail(auth.getName());

    modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
    modelAndView.addObject("project", new Project());
    modelAndView.addObject("allUsers", userService.findAll());
    modelAndView.setViewName("project_creation");
    return modelAndView;
}

@RequestMapping(value = "/projects", method=RequestMethod.POST)
public ModelAndView processProjectForm(@Valid Project project, BindingResult bindingResult) {
    ModelAndView modelAndView = new ModelAndView();
    // Get authenticated user
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    User user = userService.findByEmail(auth.getName());

    // Get all projects
    List<Project> allProjects = projectService.findAll();

    // Check if project already exists
    Project projectExist = projectService.findByTitle(project.getTitle());
    if(projectExist != null) {
        bindingResult
        .rejectValue("title", "error.project",
                "There is already a project with this title");
    }


    // Get all users
    List<User> allUsers = userService.findAll();

    if(bindingResult.hasErrors()) {
        modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
        modelAndView.addObject("project", new Project());
        modelAndView.addObject("allUsers", allUsers);
        modelAndView.setViewName("project_creation");
    } else {

        // Create project
        project.setProjectLead(user);
        projectService.saveProject(project);
        modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
        modelAndView.addObject("success", "Project successfully created!");
        modelAndView.addObject("project", new Project());
        modelAndView.addObject("projects", allProjects);
        modelAndView.setViewName("redirect:/secure/dashboard");
    }
    return modelAndView;
} 

I was able to fix the 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users''. I simply had to add a converter class telling how to convert from string to User object.

@Component
public class StringToUser implements Converter<String, User> {

@Autowired
private UserService userService;

@Override
public User convert(String arg0) {
    Integer id = new Integer(arg0);
    return userService.findOne(id);
}
}

And changed the option in my form to have a value of user.id instead of a user object. The converter would take care of the rest:

<option th:each="user : ${allUsers}" th:value="${user.getId()}" th:text="${user.getUsername()}"></option>

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