How can I reload my hibernate dependent objects before de validation - spring

I have an Spring controller with code like:
#RequestMapping("save")
public String save(#ModelAttribute #Valid Form form, BindingResult result){
if( result.hasErrors()){
[...]
My form contains a list of hibernate objects. All have their properties setted. I create an edit HTML form and in the controller I find that all the objects on the ManyToOne relationships is lost. I only have the ID. I could reload data from the database but it is too late for the validation casued by the #valid annotation.
public class Form{
#Valid
#NotNull
private List<Item> item;
#NotNull
private Foo foo;
[...]
And Item
#Entity
#Table(name = "item")
#XmlRootElement
public class Item{
#ManyToOne()
#JoinColumn(name = "dependent", referencedColumnName = "id", nullable = false)
#NotNull
private Dependent dependent;
#NotNull
private Currency currency;
How could I set the Dependent and Currency fields before the validation? Is there any alternative to reload data from the database?
(Disclaimer some names have been changes to protect the inocent)

If you are using Spring-Data-JPA you can register DomainClassConverter to do this work for you. In another case you may write such converter by yourself.

I found one way to do it:
Add to the controller a reference to SmartValidator.
#Autowired private SmartValidator validator;
Remove the #valid annotation. Reload all ManyToOne tables and call manually the validator.
#RequestMapping("save")
public String save(#ModelAttribute Form form, BindingResult result){
for(Item item : form.getItems()){
item.setDependant( myDAO.reload(item.getDependent()));
}
validator.validate(form, result);
if( result.hasErrors()){
[...]

Related

#LastModifiedDate doesn't work when only nested nested object is updated

I have a Unit entity and I'm using the #LastModifiedDate annotation to keep track of the updates. The problem is that in case I only update the items field the updateDate field isn't updated with the new date but if I update any other fields in the Unit entity the updateDate field is updated properly.
//other annotations
#EntityListeners(AuditingEntityListener.class)
public class Unit {
#Id
#GeneratedValue
private UUID id;
private String unitId;
private String unitName;
//other fields
#LastModifiedDate
private LocalDateTime updateDate;
#OneToMany(cascade = {CascadeType.ALL, CascadeType.REFRESH})
#OrderBy("slotNumber")
private List<Item> items;
}
Unit repository
public interface UnitRepo extends CrudRepository<Unit, String> {
Set<Unit> findAllByProfileUsername(String username);
}
And my update method in my Unit service
public Unit updateUnit(Unit unit) {
return repo.save(unit);
}
#LastModifiedDate will only update the modified date when the changes have been made in the entity parameters, not the relationship. When you only modify the Item, updateDate will not be updated. You may find the open issue(for mongo) related to the same.
In case you want to modify the updateDate, you may implement the entity listeners with the #PrePersist or #PreUpdate (see JPA Lifecycle). You may also have a look into AuditorAware
You can use the callback methods in your Unit Entity class.which allows to detect the changes before / after to the entity class. #PreUodate and #PostUpdate you can try.
References -
https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/listeners.html

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

Custom bean validation not working

In my Student class I have many fields which I am storing in the database and I also have one field to store photo( for that I am using MultiPartFile datatype) and I am validating this field using custom validation.
Below is code for validation
#Component
public class PhotoValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return Student.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Student student=(Student)target;
if(student.getStudentPhoto()!=null){
if(student.getStudentPhoto().getSize()==0){
errors.rejectValue("file", "missing.file");
}
}
if(!student.getStudentPhoto().getOriginalFilename().endsWith(".jpg")){
errors.rejectValue("file", "invalid.file");
}
}
}
In the controller I am implementing it like this
#InitBinder
protected void initBinderStudent(WebDataBinder binder) { binder.setValidator(photoValidator);
}
My Student Model is :-
#Entity
#Table(name = "STUDENT")
public class Student extends UrlEntity {
#Transient
MultipartFile studentPhoto;
#Column(name = "COURSE_TYPE", nullable = false)
#NotNull(message = "Course Type: Course Type can not be left blank")
private in.jmi.constants.CourseType courseType;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "STUDENT_USER")
#Valid
private User user;
This custom validation of photo is not working and it also mess up the other annotation based validation that I am having here.
I have checked many posts in stackoverflow but couldn't find any relation to this particular problem.
Note:-If I remove the validation code from controller the code works just fine doing all the validations it is supposed to do.
You are mixing approaches in your example. You are not showing the imports in your code example, but the PhotoValidator class does not implement a Bean Validation constraints. It might be some Spring/JSF specific validator!?
To implement a Bean Validation constraint, you need to define a constraint annotation and at least one implementing ConstraintValidator. This is all described in Creating custom constraints. There are plenty of examples out there how to write a custom constraint.

ID field is null in controller

I am using spring mvc with hibernate and JPA. I have a Person class which is inherited by another class called Agent. The mapping is implemented as follows:
#Entity
#Table(name = "Person")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person extends Auditable implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PersonId")
protected Long id;
//other variables
...
}
#Entity
#PrimaryKeyJoinColumn(name = "PersonId")
public class Agent extends Person implements Serializable {
//additional agent specific variables go here
...
}
Saving new data is smooth and I have no problem there. however, when I edit data, everything except the id value is bound to the controller method's model attribute. I have verified that the id has been sent along with other items from the browser using chrome's developer tools. but the id field at the controller is always null and as a result the data is not updated. This is what my controller method looks like:
#RequestMapping(value = "register", method = RequestMethod.POST)
public #ResponseBody CustomAjaxResponse saveAgent(ModelMap model, #ModelAttribute("agent") #Valid Agent agent, BindingResult result) {
...
}
I suspect the problem is probably with my inheritance mapping because I have other classes inheriting from the Person class and I face a similar problem there as well.
Please help!
you need a public setter for id.
In cases like this I commonly use a specific dto for the form, and/or implement a conversion service that retrieves the entity via hibernate based on id and then performs a merge.

Spring: How do I construct this command object through a GET HTTP request?

I'm using Spring 3.1.0.RELEASE with Hibernate 4.0.1.Final. I want to invoke a search method in a controller that takes as input a search bean (the Event bean below) ...
#RequestMapping(value = "/search_results.jsp")
public ModelAndView processSearch(final HttpServletRequest request, final Event searchBean, final BindingResult result) {
...
}
The event bean contains the following field ...
#Entity
#Table(name = "EVENTS")
public class Event implements Comparable {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="EVENT_FEED_ID")
private EventFeed eventFeed;
...
}
in which the EventFeed object contains the following fields ...
#Entity
#Table(name = "EVENT_FEEDS")
public class EventFeed {
#Id
#Column(name = "ID")
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#NotEmpty
#Column(name = "TITLE")
private String title;
...
}
How do I construct a URL such that the search bean's Event.getEventFeed().getId() field is populated?
I realize I could submit a GET request with a parameter like "eventFeedId=2" and populate everything manually, but since other pages are submitting requests that populate the command object, I'd like to continue to use the same logic.
It would be
/search_results.jsp?event.eventFeed.id=...&event.eventFeed.title=...
event is a default model attribute name as defined in #ModelAttribute, other binding rules are described in 5.4.1 Setting and getting basic and nested properties.
Note, however, that this approach can cause problems if you'll associate these bean with Hibernate session later. For example, if you want to attach new Event to the existing EventFeed by calling merge() it would also override the title property. Thus, in such a case it would be better to avoid overuse of data binding and pass primitives as parameters instead.

Resources