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.
Related
I'm learning Spring MVC and I want find a car via an id but get in return the name.
In my service class I call a generic method getXXXById. This is something JPA gives me by nature.
I know that I get the whole entity but how can I just receive the corresponding name to the id.
Example: I call getCarById(2) and it gives me back Tesla.
My Table:
id | Name
----------
1 | Ford
2 | Tesla
My Service:
class CarService {
// code ...
public Optional<CarEntity> getCarById(int id) {
return carRepository.findById(id);
}
There are two options to do that.
Making your own query
You could write your own query in JQPL to retrive only names.
For example you could create method like that in your repository.
#Query("select t.name from CarEntity where id = ?1")
public String findNameById(Integer id);
more information on this feature of Spring Data Jpa HERE
Projections
Second option is to make projection. As it is written in documentation
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to rather project on certain attributes of those types. Spring Data allows to model dedicated return types to more selectively retrieve partial views onto the managed aggregates.
In simple words, it allows you to aggregate your results form queries in some limited set of attributes rather then whole entity.
Specifically for your needs I'd suggest to use first approch, but it is worth to know both.
I use #Version in one Java class and all Java classes extends of this. okey.
I also use objectForm. I use this because it is assumed that is more secure (I hide all <form:hidden path="XX"/> from JSP), but when I open two tabs in my browser, if I modify and safe the object in the two tabs, Spring does not check that the version is lower than that of the database and save it. If I not use object form, Spring alert me that the version is lower. Yes, objectForm is obligatory.
The idea is: I send ObjectForm at JSP. I modify the object from jsp and then, I return object form to controller. This, convert the objectForm to object and save the object.
Any idea?
Some pictures:
The #Version annotation only works in Hibernate entity classes, that are directly saved into the database.
If you do the following the #Version will be validated, throwing an exception if an object with an invalid version is attempted to be saved in the database:
read an entity from the database annotated with #Version
copy entity data to a view layer object (including the version)
modify it on the view layer
on save button pressed, copy the form data including the version to a new entity object
to save the new data, call entityManager.merge()
the merge will trigger a database selection. if the version in the database is higher than the merged object you will get a StaleObjectException
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;
}
}
I am developing a simple forum web application using SpringMVC, JPA2.
I have created JPA entities like User, Forum, Post etc which reflects the DB table structure.
But while displaying the data on UI I need DTOs as I can't always hold the data to be displayed on UI using Entities.
For ex: Change Password screen. Here I need to hold Old Pwd, New Password and Confirm New Pwd. But User entity won't have Old/New/Confirm Pwd fields, it just has Password. So I need to create DTOs which are just data carriers between Web and service layers.
My question is while creating DTO objects, should I put all the properties in DTO itself or wrap the Entity in DTO and add additional properties needed?
Ex: For Edit User Screen,
public class UserDTO
{
private User user; // User is a JPA entity
// setters & getters
}
With this I can pass the underlying User entity to my service layer. But while binding UI properties to DTO I need to associate PropertyEditors.
(or)
public class UserDTO
{
private String userId;
private String userName;
private String password;
// setters & getters
}
With this approach, I need to convert & copy the DTO properties into JPA entities and pass to Service layer.
Which approach is better? Or is there any other approach without DTOs altogether?
Your first approach still carries the Entity object itself to the presentation layer. If you have additional parameters that are not coming from the database, and your persistence context is still available, then this approach is sufficient.
The second approach requires duplication of code, which is not ideal.
If the persistence context is not available, I would suggest detaching the Entity from the persistence context using EntityManager.detach(), rather than creating a parallel hierarchy of beans.
On the other hand, if data is coming in from the presentation layer, you will need to load the Entity from the database (using find() or something similar) and update it, or merge() it into the persistence context.
My team is coding an application that involves editing wikipedia-like pages.
It is similar to the problem we have with registration:
A straightforward implementation gives something like
public static void doRegistration(User user) {
//...
}
The user parameter is a JPA Entity. The User model looks something like this:
#Entity
public class User extends Model {
//some other irrelevant fields
#OneToMany(cascade = CascadeType.ALL)
public Collection<Query> queries;
#OneToMany(cascade = CascadeType.ALL)
public Collection<Activity> activities;
//...
I have read here and there that this fails. Now, in Play!, what is the best course of action we can take? There must be some way to put all that data that has to go to the server in one object, that easily can be saved into the database.
EDIT: The reason this fails is because of the validation fail. It somehow says "incorrect value" when validating collection objects. I was wondering if this can be avoided.
SOLUTION: Changing the Collection to List has resolved the issue. This is a bug that will be fixed in play 1.2 :)
Thanks beforehand
this works. To be more clear, you can define a controller method like the one you wrote:
public static void doRegistration(User user) {
//...
}
That's completely fine. Just map it to a POST route and use a #{form} to submit the object, like:
#{form id:'myId', action:#Application.doRegistration()}
#{field user.id}
[...]
#{/form}
This works. You may have problems if you don't add all the field of the entity in the form (if some fields are not editable, either use hidden inputs or a NoBinding annotation as described here).
EDIT: on the OneToMany subject, the relation will be managed by the "Many" side. That side has to keep the id of the related entity as a hidden field (with a value of object.user.id). This will solve all related issues.
It doesn't fail. If you have a running transaction, the whole thing will be persisted. Just note that transactions are usually running within services, not controllers, so you should pass it from the controller to the service.