It's a really simple question.
I would like to know what's the best practice for submitting a huge html form to a Spring MVC #Controller (huge = more than 20 fields / complex fields as list and so on...)
I'm a little bit confused because somebody use this approach (from the official examples):
#RequestMapping( value = "/users" , method = RequestMethod.POST )
public ModelAndView saveUser(Locale locale, #Valid User user, BindingResult result) {
if (result.hasErrors()) {
logger.error("Errori form:: " + result.getErrorCount());
} else {
logger.info("Utente salvato");
userService.saveUser(user);
}
...
return mav;
}
and some others use the more complex SimpleFormController this way:
Spring-MVC forms on GAE
I surely do prefer the first way but I'm worried I will have to create many "FormBeans", useless DTOs.
Can you explain me differences and give me advices?
Thank you.
What this example you purposed is doing is using Spring validation. I think that you should look at spring manual or some help, as it is very basic, but the general idea is that Spring is validates the form for you.
First, you have to create a Pojo (create a Class with all the inputs from the form, with getters and setters).
Then, you have to use spring forms, which are slightly different to normal forms. The basic idea is that you map an object (User in your case) to the form. And then, each of the inputs, is mapped to a field of the Pojo.
After that, you add the validation to the Pojo, with annotations.
#Size(max = 10)
private String name;
For example, this annotation Size indicates that field name must be 10 chars as max.
This validations, are checked with the annotation #Valid.
Then, when hasErrors is called, you can get if the form has errors.
Related
Let's consider we have two entities. User entity can have many offers and an Offer must have one User.
class User {
...
#OneToMany(mappedBy = "user", orphanRemoval = true)
private Set<Offer> offers;
}
class Offer {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private User user;
}
At this moment there are two controllers. UserController and the OrderController. The UserController is mapped under /api/v1/users/ and the OrderController is mapped under /api/v1/orders/.
What should an endpoint look like that fetches the user's offer list?
Should it be in the same controller? I do have by functionality project structure.
How to modify or delete an Offer for a particular User? In case we would have /api/v1/users/{username}/offers/{offerId} to delete or update an offer, should we have also /api/v1/offers/{offerId} endpoint that allows to edit or remove an offer? Perhaps it is worth having it for an administrator?
General rules of thumb that I use when creating my endpoints are:
URLs need to be clean and easy to understand
They should be as short as possible while still informative.
Try to build it in a such manner that it allows you to reuse it within the reasonable amount
Think about the user experience(whether this is called from browser or mobile app etc.)
I am not sure if there is a written rule exactly how one should build an URL though.
In your specific case I would use /users/{username}/offers/{offerId} only if this is the only place you are exposing and using offers, since you are separating your code by functionality.
If you have any further logic around offers and/or have beans that have such logic I would create a separate controller for Offers which would be under /offers.
Concerning your last question. This very much depends on what you are trying to achieve. If you need to be able to update/delete/create offers then it makes sense to have such functionality. Even if it only used by the administrator. You can restrict the access to the endpoint. How to do that depends on the way you are authorize your users and the information that you have on them. Most people use roles.
If you decide to have the full CRUD functionality I would suggest to use a single path with combination of request methods.
Personally I would create the following:
#RestController
#RequestMapping(value = "/users")
class UserController {
#GetMapping("{userId}/offers")
public Set<Offer> getAllOffers(#PathVariable("userId") String userId){
...
}
#GetMapping("{userId}/offers")
public Offer getOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
#PutMapping("{userId}/offers")
public Offer createOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#PostMapping("{userId}/offers")
public Offer updateOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#DeleteMapping("{userId}/offers")
public void deleteOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
}
In this scenario I think the POST/PUT for create and update will be cleaner as there will be no duplication of information. To be precise the IDs.
I agree that it should be in the same 'UserController', it makes sense because offers belong to the user, so having an endpoint like:
#GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(#PathVariable("user") String user) {
return offerService.getOffers(user);
}
You can define special DTOs for getting the meta-data from the offers if you wanted to display them in a list for example, and you could display them as a list to your user.
You could set up a similar endpoint for updating the offer which could be a POST endpoint, and a DELETE endpoint for deleting. You might want to think about what would happen if the user is looking at an offer when you delete it though, like making an asynchronous task for deleting the offer in a background thread and updating the UI to inform the user that the offer is deleted.
Spring has some really nice annotations for security stuff (check this and this), you could write your own annotation for administrator endpoints:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAuthority('" + ROLE_ADMIN + "')")
public #interface IsAdmin {}
Then annotate your method like this:
#DeleteMapping("/{user}/{offer}/delete")
#IsReceiverAdmin
public void delete(#PathVariable("user") String user, #PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
Of course the implementation of the service layer would be quite important, but it could be as simple as calling your repositories and performing the operations there :)
So far I have done one (REST) project using Spring Boot and liked it a lot. The one thing I found to be somewhat devious was my understanding of #RequestBody.
Suppose I have the POST method below to login a user. My user entity may contain attributes other than just the username and password I'd like the post-request to have. In this case I saw no other option but to make an extra object (LoginRequest) to hold the data for the incoming data.
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> login(#RequestBody LoginRequest request) {
User p = null;
if (request != null) {
p = User.login(request.getEmail(), request.getPassword()); // validates and returns user if exists
if (p != null){
return new ResponseEntity<User>(p, HttpStatus.OK);
}
}
throw new IllegalArgumentException("Password or email incorrect");
}
Similarly, I'd like the #ResponseBody to return a minimized version of the User object where for example the password is excluded.
What are some of the standard approaches to this issue? Do I really have to make a separate object for every 'json-view'? I did some REST stuff in python before and here I would just have a Dictionary containing the request attributes. Any similar approaches?
Create a new entity if you will persist or operate on it
If you need a custom view for existing entity (add/remove fields) DTO with custom serialization can be used
There maybe a case when you don't want to create DTO because you will not reuse it anywhere, but need some quick solution for custom response you can use Map<String, Object> - the key will be used for JSON field name, and Object is for the value, for example:
#RequestMapping(method = RequestMethod.POST)
public Map<String, Object> login(#RequestParam Integer p1) {
Map<String, Object> map = new HashMap<>();
map.put("p1", p1);
map.put("somethingElse", "456");
return map;
}
JSON response:
{
"p1": p1value,
"somethingElse": "456"
}
3rd case will suite you if you're not building too complex bodies with nested objects which should be customized based on some conditions. Use 2nd option in the opposite case.
There are two approaches to this, for both Request and Response -
1. Use Same entities with extra params and populate only what you need. You can check for nulls.
2. Use separate Data Transfer Objects (DTOs) - These basically can be used when entities have different fields than the object you need to transfer.
Personally, I like the first approach to save the effort of mapping entities to DTOs and back. But, sometimes when DTOs need to be totally different, we need to use the second approach. Eg. APIs for dashboards numbers and reports.
HTH
I have a very simple task,
I have a "User" Entity.
This user has tons of fields, for example :
firstName
age
country
.....
My goal is to expose a simple controller for update:
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(data)
I would like clients to call my controller with updates that might include one or more fields to be updated.
What are the best practices to implement such method?
One naive solution will be to send from the client the whole entity, and in the server just override all fields, but that seems very inefficient.
another naive and bad solution might be the following:
#Transactional
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(int userId, String[] fieldNames, String[] values) {
User user = this.userDao.findById(userId);
for (int i=0 ; i < fieldsNames.length ; i++) {
String fieldName = fieldsName[i];
switch(fieldName) {
case fieldName.equals("age") {
user.setAge(values[i]);
}
case fieldName.equals("firstName") {
user.setFirstName(values[i]);
}
....
}
}
}
Obviously these solutions aren't serious, there must be a more robust\generic way of doing that (reflection maybe).
Any ideas?
I once did this genetically using Jackson. It has a very convenient ObjectMapper.readerForUpdating(Object) method that can read values from a JsonNode/Tree onto an existing object.
The controller/service
#PATCH
#Transactional
public DomainObject partialUpdate (Long id, JsonNode data) {
DomainObject o = repository.get(id);
return objectMapper.readerForUpdating(o).readValue(data);
}
That was it. We used Jersey to expose the services as REST Web services, hence the #PATCH annotation.
As to whether this is a controller or a service: it handles raw transfer data (the JsonNode), but to work efficiently it needs to be transactional (Changes made by the reader are flushed to the database when the transaction commits. Reading the object in the same transaction allows hibernate to dynamically update only the changed fields).
If your User entity doesn't contains any security fields like login or password, you can simply use it as model attribute. In this case all fields will be updated automatically from the form inputs, those fields that are not supose to be updated, like id should be hidden fields on the form.
If you don't want to expose all your entity propeties to the presentation layer you can use pojo aka command to mapp all needed fields from user entity
BWT It is really bad practice to make your controller methods transactional. You should separate your application layers. You need to have service. This is the layer where #Transactional annotation belongs to. You do all the logic there before crud operations.
I have what appears to be a common problem within spring-mvc. Several of my domain object have fields that are not updatable so in my view I am not binding these fields.
For competeness sake The way these are excluded from the view is by editing the spring-roo scaffolded view setting the render attribute on the parameter to false.
As spring-mvc creates a new instance of the object rather than updating the existing object these fields are null. This means however that the object fails its validation before the control reaches the controller.
A lot of my entities will have extra fields that are not updatable in the view so I'd like to be able to come up with a generic solution rather than continually doing the same work over and over again (violating DRY).
How can one allow validation to occur in a consistent manner if fields are omitted from the view?
#RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String UserController.update(#Valid User user, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
populateEditForm(uiModel, user);
return "admin/users/update";
}
uiModel.asMap().clear();
user.merge();
return "redirect:/admin/users/" + encodeUrlPathSegment(user.getId().toString(), httpServletRequest);
}
Possible Solutions:
Omit #Valid annotation from the controller.
Pros
Easy to implement.
Easy to understand.
Cons
Means changing the controller method for every update on every object.
Validation is not occuring in the same place as all of the rest of the application.
No easy way to return the binding errors back to the view (need to validate the object afterwards)
Add Custom Validator for methods that need omitted fields
Example:
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
if (request.getMethod().equals("PUT")) {
binder.setDisallowedFields("registrationDate", "password");
Validator validator = binder.getValidator();
Validator userUpdateValidator = new UserUpdateValidator();
binder.setValidator(userUpdateValidator);
}
}
Pros
Clear flow.
Cons
Suffers wildly from DRY problems. This means that If the domain object is altered in any way I need to revalidate.
Field validation is not the same as Hibernate validation when saving.
No tangible benefits over omitting validation and manually validating.
Would consider if?
Custom validator could delegate to standard JSR-303 validator but just omit fields.
Remove JSR-303 annotations from the domain object
Not an option this means that there is no validation on an object before saving. Worse I believe it will affect the DDL that is producted for database, removing constraints from the DB itself. Only put in here for completeness sake
Lookup domain object before validation occurs
The idea of this solution is to lookup the existing domain object before updating. Copying any not null fields to the old object from the request.
Pros
- The validation can go through the normal cycle.
- The validation doesn't need to change depending on what method you are implying.
Cons
Database access before hitting the controller has a bit of a smell.
I can't see any way to implement this.
Won't work for fields that need to be omitted during other stages of the object lifecycle. For example if adding a timestamp during creation.
I would like to know how to implement either a validator that delegates to the standard JSR-303 validator or alternatively how to lookup the object before modifying it. Or if anyone has any other possible solutions?
Either of these solutions allow for the treatment to be consistent over multiple objects.
Hopefully either would allow for added annotations such as.
#RooCreateOnly which means the domain object could be annotated as such leaving all the validation definitions in the one place.
The last option can be achieved with the #ModelAttribute annotation.
Create a method that returns your domain object and add the #ModelAttribute annotation to it. Then add the same annotation to the domain object argument of the method where you want to use that object. Spring will first load the object from the ModelAttribute method then merge it with the posted data.
Example:
#ModelAttribute("foobar")
public User fetchUser() {
return loadUser();
}
#RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String update(#ModelAttribute("foobar") #Valid User user, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
return etc();
}
You can use the disabled property for the input tags in your jspx file containing the form for the fields that you want to mark as read-only.
Also make sure you clear the z attribute relating the field so that Roo will ignore the tag if there is any change made to the entity later on.
Cheers!
I'm posting another answer totally unrelated to my previous one.
There is another solution: wrap your domain object into special form object that only expose the fields you want to validate.
Example:
public class UserForm {
private final User user = new User();
// User has many fields, but here we only want lastName
#NotEmpty // Or whatever validation you want
public String getLastName() {
return this.user.getLastName();
}
public void setLastName(String lastName) {
this.user.setLastName(lastName);
}
public User getUser() {
return this.user;
}
}
The Spring MVC binding mechanism is powerful, but I'm now confronted with a trivial issue that I wonder how to resolve:
User JPA entity, that is used for the binding and validation as well (i.e. throughout all layers)
"Edit profile" page, that is not supposed to change the password or some other entity properties
Two ways that I can think of:
Using the same object
use #InitBinder to configure a list of disallowed properties
obtain the target user (by id)
then use a reflection utility (BeanUtils) to copy the submitted object to the target object, but ignore null values - i.e. fields that are not submitted
Introduce a new object that has the needed subset of fields, and use BeanUtils.copyProperties(..) to merge it to the entity.
Alternatives?
I've found that as soon as your web model starts to diverge from your business layer in function, it's best to use a view layer object (a model object) to collect, or display the data
the entity:
public class com.myapp.domain.UserEntity {
}
the model object:
public class com.myapp.somesite.web.SomeSiteUserModel {
public static SomeSiteUserModel from(UserEntity userEntity) {
... initialize model ...
}
public UserEntity getModelObject() {
... get entity back ...
}
}
now all view based operations can hand off processing to the internal model object if that makes sense, otherwise it can customize them itself. Of course the problem with this is you have to re-write all the getters and setters you want for the entity (an issue that I've had to deal with, that is annoying) unfortunately that is a bit of a Java language issue
I just checked up with two of the last Spring projects I have worked on and in both places the following approach is taken:
In the JSP page for the form the change password field has a name that does not match the name of the password field in the User bean, so that it doesn't get mapped to the bean. Then in the onSubmit method there is a separate check whether a new password has been submitted, and if it has been, the change is reflected explicitly.
Поздрави,
Vassil
You can read the object from the database first and bind then the request. You can find an example at FuWeSta-Sample.
It uses a helper-bean which must be initialized by Spring.