I have a user entity that has many attributes (some fields not shown here):
#Entity
public class User {
#OneToOne(cascade = ALL, orphanRemoval = true)
private File avatar; // File is a custom class I have created
#NotEmpty
#NaturalId
private String name;
#Size(min = 6)
private String password;
#Enumerated(EnumType.STRING)
private Role role;
}
In my thymeleaf template I have a form that submits username, password and avatar (MultipartFile). Now in my controller instead of these parameters...
#PostMapping("/register")
public String register(#RequestParam String username,
#RequestParam String password,
#RequestParam MultipartFile avatar) { ...
...I want to use #ModelAttribute #Valid User user. My problem is that:
password first should be encrypted then passed to the user entity,
bytes[] from MultipartFile should be extracted then stored in user entity (as a custom File object),
some other fields such as Role should be set manually in the service class.
How can I take advantage of #ModelAttribute?
Instead of trying to shoehorn everything into your User class, write a UserDto or UserForm which you can convert from/to a User. The UserForm would be specialized for the web and converted to a User later on.
The conversions you are talking about should be done in your controller (as that is ideally only a conversion layer before actually talking to your business services).
public class UserForm {
private MultipartFile avatar;
#NotEmpty
private String username;
#Size(min = 6)
#NotEmpty
private String password;
public UserForm() {}
public UserForm(String username) {
this.username = username;
}
static UserForm of(User user) {
return new UserForm(user.getUsername());
}
// getters/setters omitted for brevity
}
Then in your controller do what you intended to do (something like this):
#PostMapping("/register")
public String register(#ModelAttribute("user") UserForm userForm, BindingResult bindingResult) {
if (!bindingResult.hasErrors()) {
User user = new User();
user.setName(userForm.getUsername());
user.setPassword(encrypt(userForm.getPassword());
user.setAvataor(createFile(userForm.getAvatar());
userService.register(user);
return "success";
} else {
return "register";
}
}
This way you have a specialized object to fix your web based use cases, whilst keeping your actual User object clean.
Maybe you can just use a setter to make all these actions. When Spring is mapping data to fields, and you have setters in the entity, it will use them to pass data. You can preprocess data in this way and set final values to fields.
#PostMapping("/register")
public String register(#ModelAttribute User user, Model model) { // remember if You have another name for parameter and backing bean You should type this one #ModelAttribute(name="userFromTemplate") User user
encryptPassword(user.getPassword); //remember that is sample code, You can do it however You want to
extractBytes(user.getAvatar); //same here
user.setRole(manuallySetRole);
model.addAttribute("user", user);
return "success"; // here u can redirect to ur another html which will contain info about ur user
} else
return "redirect:sorry";
}
encryptPassword(String password) { ... }
same for the rest methods
Here i give You sample code how to use #ModelAttribute in your example. If You have questions feel free to comment.
Related
I want to ignore some fields during deserialization of json data in spring. I cannot use #JsonIgnore as the same model will be used in different methods and different fields need to be ignored.
I have tried to explain the situation with the below example.
class User
{
private String name;
private Integer id;
//getters and setters
}
This is the User class that will be used as model.
#RequestMapping(value = '/path1', method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<CustomResponse> loadUser1(#RequestBody User user){
System.out.println(user.name);
//user.id is not required here
}
This is the first method that will use user.name and ignore user.id.
#RequestMapping(value = '/path2', method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<CustomResponse> loadUser2(#RequestBody User user){
System.out.println(user.id);
//user.name is not required here
}
This is the second method that will use user.id and ignore user.name.
You can use #JsonFilter to achieve dynamic filtering.
First, create a filter using com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter and pass it to a filter provider com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider.
Then, declare this filter on your bean using #JsonFilter.
In your case, this will do it:
#JsonFilter("myFilter")
public class User {
private String name;
private int id;
// Getters and setters
}
This will apply the filter on your POJO:
public MappingJacksonValue getFiltered() {
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("myFilter", SimpleBeanPropertyFilter.filterOutAllExcept("id"));
User user = new User();
user.setId(1);
user.setName("Me");
MappingJacksonValue jacksonValue = new MappingJacksonValue(user);
jacksonValue.setFilters(filterProvider);
return jacksonValue;
}
Edit:
SimpleBeanPropertyFilter has factory methods to tender to almost all practical filtering scenarios. Use them appropriately.
I have used the #JsonIgnore annotation to prevent exposing the password to the user while sending user details to the user:
public class UserDto {
private String username;
#JsonIgnore
private String password;
}
Below is the response of user API:
{
"username": "test12"
}
But while saving the new user when I am hitting the save API and send
below data, my controller method is consuming null password because of
#JsonIgnore and getting null pointer exception;
{
"username": "test1225",
"password": "admin"
}
Controller method:
#RequestMapping(value = "/addAccount", method = RequestMethod.POST)
public ResponseEntity<Void> addAccount(#RequestBody UserDto userDto) {
User user = new User();
user.setUsername(userDto.getUsername());
user.setPassword(userDto.getPassword());
userService.saveUser(user);
return new ResponseEntity<Void>(HttpStatus.CREATED);
}
Is there any way to ignore the Password parameter when returning
response to the user and not to ignore the password field value when
getting password parameter in request body in controller method?
You can use JsonViews to specify which fields should be included during serialization/deserialization. Take a look at this blog post to learn about JsonViews - http://www.baeldung.com/jackson-json-view-annotation.
For your issue, you can create a view called UserResponse.
public class UserResponse {
}
And annotate the fields of UserDto which you want to return with #JsonView(UserResponse.class)
public class UserDto {
#JsonView(UserResponse.class)
private String username;
private String password;
}
And in your controller, add JsonView annotation on the return type.
public #JsonView(UserResponse.class) ResponseEntity<Void> addAccount(#RequestBody UserDto userDto) {
What is the proper way to handle editing objects in Spring MVC. Let's say I have user object:
public class User {
private Integer id;
private String firstName;
private String lastName;
//Lets assume here are next 10 fields...
//getters and setters
}
Now in my controller I have GET and POST for url: user/edit/{id}
#RequestMapping(value = "/user/edit/{user_id}", method = RequestMethod.GET)
public String editUser(#PathVariable Long user_id, Model model) {
model.addAttribute("userForm", userService.getUserByID(user_id));
return "/panels/user/editUser";
}
#RequestMapping(value = "/user/edit/{user_id}", method = RequestMethod.POST)
public String editUser(#Valid #ModelAttribute("userForm") User userForm,
BindingResult result, #PathVariable String user_id, Model model) {
if(result.hasErrors()) {
User user = userService.getById(user_id);
user.updateFields(userForm);
}
userService.update(user);
}
Now the question is do I really need to get my user from database in POST method and update every field one by one in some update method or is there better way for that?
I am thinking about using #PathVariable for User and get User from database with converter and then in some way inject parameters from POST method into that object automatically. Something like this:
#RequestMapping(value = "/user/edit/{user}", method = RequestMethod.POST)
public String editUser(#Valid #PathVariable("user") User userForm,
BindingResult result, Model model)
But when I try this I got error with BindingResults:
java.lang.IllegalStateException: An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments
Is there any easy way to create controller to handle objects editing or do I need to copy fields which could change one by one??
By the way, I can't use SessionAttributes because it causes problems for multiple tabs.
I believe you are sending "userForm" as a model attribute. If so try with following pattern,
#RequestMapping(value = "/user/edit/{user_id}", method = RequestMethod.POST)
public String editUser(#PathVariable String user_id, #Valid #ModelAttribute("userForm") User userForm,
BindingResult result, Model model)
Thanks
You keep user id in a input hidden inside your edit form.
#RequestMapping(value = "/user/edit", method = RequestMethod.POST)
public String editUser(#Valid #ModelAttribute("userForm") User userForm,
BindingResult result,Model model){
if(result.hasErrors()){
User user = userService.getById(userForm.getId());
user.updateFields(userForm);
}
userService.update(user);
return "redirect:.......";
}
I am using #RequestParam to catch the front-end user input and pass it to the back end through controller and save it to the database.
So far controller handles the request like this:
#RequestMapping(value = "/myURL", method = RequestMethod.POST)
public String saveCustomer(
#RequestParam("customerFirstName") String customerFirstName,
#RequestParam("customerLastName") String customerLastName,) {
Customer customer = customerService.saveCustomer(
customerFirstName, customerLastName);
return null;
}
Well I guess this is fine when I only have two #RequestParam for two arguements, but I am facing some table that has more than 10 params, I think by using #RequestParam is apparently not realistic, is there another around this?
You can save the customer directly.
#RequestMapping(value = "/myURL", method = RequestMethod.POST)
public String saveCustomer(Customer customer) {
customerService.saveCustomer(customer);
return null;
}
Spring can databind POJOs as long as you have a no-args constructor and setters for your properties.
You should create a provider to use JSON, so you can send complex Java\JS objects and not just primitives.
If you are going to deal with multiple #RequestParam you can always opt for a bean class approach.
Controller:
#RequestMapping(value = "/myURL", method = RequestMethod.POST)
public String saveCustomer(
#RequestBody Customer customer) {
return null;
}
Bean:
public class Customer{
private String customerFirstName;
private String customerLastName;
//constructors, getters and setters
}
Check this link out for more info on #RequestBody
I have a Contact object that I put in the request, this object is modified in the
form and then get the modified object. I would like the object that is back is the same object that you send, you keep the value of the attributes that were not in the form.
class Contact{
private String name; // this attributes will be modified
private String lastName;
private Long id;
private Date created; // this atributes will not be modified
// getters and setters ....
}
#RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
public String updateContact(#PathVariable("id") Long id, Model model) {
Contact c = contactDao.get(id);
model.addAttribute("contact", c);
return "contact/form";
}
#RequestMapping(value = "/{id}/edit", method = RequestMethod.POST)
public String update(#PathVariable("id") Long id, #Valid #ModelAttribute Contact contact, BindingResult result, Model model) {
// The contact I get here I want to keep the original attributes of the
// object sent, and have the changes in the fields shown on the form. is that possible?
return "redirect:/contact";
}
<form:form action="${pageContext.servletContext.contextPath}/tags/create" commandName="contact">
<form:input path="name"/>
<form:errors path="name" cssClass="formError"/>
<form:input path="lastName"/>
</form:form>
I do not want to use hidden fields to maintain the value of the attributes that will not be changing
If you only want some of the fields to be handled in a form, make a new class - ContactDTO that contains only them, and then manually (or through reflection) copy them to the original Contact object (which you load by id from the DB)
I found the solution to the problem by stating the contact object as an object that lives in the session
#Controller
#RequestMapping("/contact")
#SessionAttributes("contact")
public class ContactController {
....
....
#RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
public String updateContact(#PathVariable("id") Long id, Model model) {
Contact c = contactDao.get(id);
model.addAttribute("contact", c);
return "contact/form";
}
#RequestMapping(value = "/{id}/edit", method = RequestMethod.POST)
public String update(#PathVariable("id") Long id, #Valid #ModelAttribute Contact contact, BindingResult result, Model model) {
contactDao.update(contact);
return "redirect:/contact";
}
}
What is your persistence framework? is it JPA or Hibernate? If so, annotate the field with #Column(updatable=false)