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.
Related
Hi I have implemented a mock solution to my problem and I'm pretty sure something better already exist.
Here's that I want to achieve :
I have created a point to load categories with or without subCategories
/api/categories/1?fields=subCategories
returns
{
"id":"1",
"name":"test",
"subCategories":[{
"id":"1",
"name":"test123"
}]
}
/api/categories/1
returns
{
"id":"1",
"name":"test"
}
My entities
#Entity
class Category{
#Id
private String id;
private String name;
private Set<SubCategory> subCategories;
}
#Entity
class SubCategory{
#Id
private String id;
private String name;
}
I have removed services since this is not the point.
I've created CategoryDTO and SubCategoryDTO classes with the same fields as Category and SubCategory
The converter
class CategoryDTOConverter{
CategoryDTO convert(Category category,String fields){
CategoryDTO dto=new CategoryDTO();
dto.setName(category.getName());
if(StringUtils.isNotBlank(fields) && fields.contains("subCategories"){
category.getSubCategories().forEach(s->{
dto.getSubcategories().add(SubCategoryDTOConverter.convert(s));
}
}
}
}
I used com.cosium.spring.data.jpa.entity.graph.repository to create an EntityGraph from a list of attribute path
#Repository
interface CategoryRepository extends EntityGraphJpaRepository<Category, String>{
Optional<T> findById(String id,EntityGraph entityGraph);
}
Controller
#RestController
#CrossOrigin
#RequestMapping("/categories")
public class CategoryController {
#GetMapping(value = "/{id}")
public ResponseEntity<CategoryDTO> get(#PathVariable("id") String id, #RequestParam(value="fields",required=false) String fields ) throws Exception {
Optional<Category> categOpt=repository.findById(id,fields!=null?EntityGraphUtils.fromAttributePaths(fields):null);
if(categOpt.isEmpty())
throws new NotFoundException();
return ResponseEntity.ok(categoryDTOConverter.convert(categOpt.get(),fields);
}
}
This is a simple example to illustrate what I need to do
I don't want to load fields that clients doesn't want to use
How could I do this in a better way ?
Take a look at GraphQL since it is a perfect match for your use case. With GraphQL it is the client that decides which attributes it wants to receive by providing in the POST request body exactly which attributes are needed to be included in the response. This is way more manageable than trying to handle all this on your own.
Spring Boot recently added its own Spring GraphQL library, so it is quite simple to integrate it in your Spring Boot app.
I have a form in which :
firstname and lastname are mandatory fields for registered user.
ssn for new user.
contract number for owner.
So, on clicking the submit button, REST API (connect API) is called with values
from either of the above groups.
My bean class has members :
FN
LN
SSN
contractNum
How do I validate using bean/hibernate validator and identify which group has been passed ?
From the Hibernate Documentation, you can read for detail
https://hibernate.org/validator/
Hibernate Validator allows to express and validate application
constraints. The default metadata source are annotations, with the
ability to override and extend through the use of XML. It is not tied
to a specific application tier or programming model and is available
for both server and client application programming. But a simple
example says more than 1000 words:
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class User {
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String ssn;
}
Bean Validation is best used for simple validation logic. If your validation requires more complexity, use Spring's Validator interface instead.
I don't know the context domain, so I'll just call your bean "Form" with all String fields for the example:
public class Form {
private String firstName;
private String lastName;
private String ssn;
private String contractNumber;
// getters and setters
}
Then create a validator for this class:
public class FormValidator implements Validator {
public boolean supports(Class clazz) {
return Form.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
Form form = (Form) target;
// validation logic
}
}
Then you can simply use it like this:
Form form = ...;
Validator validator = new FormValidator();
Errors errors = new Errors();
validator.validate(form, errors);
if (errors.hasErrors() {
// not valid
} else {
// is valid
}
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.
I seem to be baffled on how JPA Repositories are suppose to work.
In a nut-shell
#Entity
public class User extends AbstractEntity {
protected final static String FK_NAME = "USER_ID";
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name = "userId")
private List<Detail> details = new ArrayList<Detail>();
}
#Entity
public class Detail extends AbstractEntity {
Long userId;
String hello;
}
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByFirstName(#Param("firstName") String firstName);
}
And here is the only controller in the app:
#RestController
public class Home {
#Autowired
UserRepository userRepository;
#Autowired
DetailsRepository loanRepository;
#RequestMapping(value = "")
public HttpEntity home() {
User user = userRepository.findByFirstName("John");
if (user == null) {
user = new User();
user.setFirstName("John");
}
Detail detail = new Detail();
detail.setHello("Hello Msh");
user.getDetails().add(detail);
userRepository.save(user);
return new ResponseEntity("hi", HttpStatus.OK);
}
}
Below a screenshot from debugging session where the app just started and the get request to home() method creates new user, new detail, adds detail to user.
Below example - when the user is saved, the detail entity gets updated
Now on the next request, the old user John is found and has been added a new instance of detail.
The old user has been saved but now the newly created detail does not get updated outside.
How come this only works first time ?
Basically theres so much fail going on so that I would advise you to go a step backwards. If youre wana go the short path of getting a solution for exactly this problem continue reading ;)
First part related to the answer of Jaiwo99:
As I can see in the gradle view of intellij, your using Spring Boot. So it is necessary to place #EnableTransactionManagement on top of your configuration class. Otherwise the #Transacion annotation does not have any effect.
Second part your JPA/Hibernate model mapping. Theres so much bad practise on the net that it is no wonder that most beginners have troubles starting with it.
A correct version could look like (not tested)
#Entity
public class User extends AbstractEntity {
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy="user")
private List<Detail> details = new ArrayList<Detail>();
public void addDetail(Detail detail) {
details.add(detail);
detail.setUser(user);
}
}
#Entity
public class Detail extends AbstractEntity {
#ManyToOne
private User user;
private String hello;
public void setUser(User user){
this.user = user;
}
}
Some general advice related to creating a model mapping:
avoid bi-directional mappings whenever possible
cascade is a decision made on the service level and not at the model level and can have huge drawbacks. So for beginners avoid it.
I have no idea why people like to put JoinColumn, JoinTable and whatever join annotation on top of fields. The only reason to do this is when you have a legacy db (my opinion). When you do not like the names created by your jpa provider, provide a different naming strategy.
I would provide a custom name for the user class, because this is in some databases a reserved word.
Very simple, the first time you saved a new entity outside of hibernate session, the second time, the user object you got is a detached object, by default hibernate will not consider it is changed in this case.
*solution *
Move this logic to another service class, which annotated with #transactional
Or
Annotate your controller with transactional
Or
Override equals and hashCode method on user class may also help
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()){
[...]