Validation in Spring Boot - spring-boot

I don't why when I submit a POST request with an invalid numSeatAvailable. No error is return. I don't know if I missed anything. Could anyone help me please?
This is my request Class
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.sql.Time;
import java.sql.Date;
#Setter
#Getter
#ToString
public class RideRequest {
private Date date;
private Time time;
private String destination;
private String modelName;
private int modelYear;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int numSeatAvailable;
private String rideIntro;
private Long driverId;
private Long rideId;
}
This is my controller
#PostMapping("/createRide")
public String createRide(#Valid #RequestBody RideRequest rideRequest) {
System.out.println("Hello");
rideService.createRide(rideRequest);
return "created";
}

In your controller, you must return message, info when the DTO is not valid.
About validation, you have 3 ways:
(1) Add Hibernate validator dependency
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.6.Final</version>
</dependency>
Then add annotations for validate bean (DTO: Data Transfer Object).
(2) Ah, have an easy way for you: Use spring-boot-starter-validator (it is also a derived of Hibernate validator with pre and auto configuration). See tutorial at
https://reflectoring.io/bean-validation-with-spring-boot/ . I suggest you use this way.
(3) Use Java validation (standard library). Guide:
https://spring.io/guides/gs/validating-form-input/

You can use javax.validation.constraints Library
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version><!--$NO-MVN-MAN-VER$ -->
</dependency>
Add your constraitns to your DTO Model
for example :
#NotNull(groups = ProductCreation.class,message = "fieldName.notSetted")
#NotBlank(groups = ProductUpdate.class,message = "fieldName.notSetted")
#Min(value=1, message="fieldName must be equal or greater than 0")
And you can make function for validation :
private void checkIfReadyToSave(ClassDTO yourDTO) {
Set<ConstraintViolation<ClassDTO>> violations =
validator.validate(yourDTO, groupCreation.class);
if (!violations.isEmpty()) {
Map<String, Object> body = new LinkedHashMap<>();
Set<String> keys =
violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toSet());
body.put("errors", keys);
throw new BeanValidationException(ErrorConstants.ClassDTO_MISSING_ERROR_MSG, body.toString());
}
}

Related

Spring can't find implementation

Here is my folder structure:
In my IAppUserMapper I have a method to convert every AppUser entity instance to Data Transfer Object Model. Here is the code in IAppUserMapper interface:
import com.server.ecommerceapp.dto.AppUserDTO;
import com.server.ecommerceapp.model.AppUser;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
#Mapper
public interface IAppUserMapper {
IAppUserMapper appUserMapper = Mappers.getMapper(IAppUserMapper.class);
#Mapping(target = "username")
#Mapping(target = "email")
#Mapping(target = "password")
#Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
AppUserDTO toAppUserDTO(AppUser appUser);
default String getRoleName(Role role) {
return role.getRoleName();
}
}
And here is the MapperConfiguration class code where I configure both Product and user mappers:
import com.server.ecommerceapp.mapper.IAppUserMapper;
import com.server.ecommerceapp.mapper.IProductMapper;
import org.mapstruct.factory.Mappers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MapperConfiguration {
#Bean
public IAppUserMapper appUserMapper() {
return Mappers.getMapper(IAppUserMapper.class);
}
#Bean
public IProductMapper productMapper() {
return Mappers.getMapper(IProductMapper.class);
}
}
The error I get:
Error creating bean with name 'appUserMapper' defined in class path
resource
[com/server/ecommerceapp/configuration/MapperConfiguration.class]:
Bean instantiation via factory method failed; nested exception is
org.springframework.beans.BeanInstantiationException: Failed to
instantiate [com.server.ecommerceapp.mapper.IAppUserMapper]: Factory
method 'appUserMapper' threw exception; nested exception is
java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot
find implementation for com.server.ecommerceapp.mapper.IAppUserMapper
I was told I should make META-INF package in resources, with service package and the com.server.ecommerceapp.mapper.AppUserMapper txt with the content same as the name of the file, so that Spring can scan and find the package following the path:
src/main/resources/META-INF/service/com.server.ecommerceapp.mapper.AppUserMapper
but it didnt work. Any ideas how to solve this, and by the way, is it bad practise to start interface names with capital I cause Im coming from ASP?
Edit:
I added #Mapper(componentModel = "spring") to my interfaces and implemented them as DI with Autowired. I dont know if its related to that problem that I had but now I get error that it cant find collectors. Im trying to map a collection of Roles from AppUser to AppUserDTO. Here are both AppUser and AppUserDTO classes:
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
public class AppUser {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "email", nullable = false, unique = true)
private String email;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = EAGER)
private Collection<Role> roles;
}
And DTO:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class AppUserDTO {
private String username;
private String email;
private String password;
private Collection<String> roles;
}
So you're using Spring, but you are trying to not use Spring.
You should make your mappers use Spring component model:
#Mapper(componentModel = "spring")
public interface MyMapper {
Target map(Source source);
}
Check docs for dependency injection: https://mapstruct.org/documentation/stable/reference/html/#using-dependency-injection
Or do it with shared configuration: https://mapstruct.org/documentation/stable/reference/html/#shared-configurations
After that you can just #Autowired MyMapper myMapper; as any other Spring bean. No need to create instance in interface (the "Mappers.getMapper" thing) and no need to create mappers in java configuration, bean creation will be handled by framework.
#Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
now I get error that it cant find collectors
You are using an expression with Collectors class. As stated in the documentation https://mapstruct.org/documentation/stable/reference/html/#expressions:
Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the TimeAndFormat class (unless it’s used otherwise explicitly in the SourceTargetMapper). This can be resolved by defining imports on the #Mapper annotation.
So you either need to fully qualify java.util.stream.Collectors in your expression or set "imports" parameter in #Mapper annotation: #Mapper(imports = Collectors.class).
I would also say, you could just write a normal Java method for roles mapping and not be dealing with expressions. But that's up to your taste.
The file name of the service should be the interface and its content the implementation. You have named it by the implementation.

Builder class does not have build method (name: 'build') - Jackson

This error occurs when objectMapper.convertValue(cityEntity, City.class)) is called.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Builder class com.example.PostgresApp.dto.City$Builder does not have build method (name: 'build')
package com.example.PostgresApp.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.*;
import org.apache.commons.lang3.StringUtils;
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = City.Builder.class)
public class City {
String name;
String description;
#JsonPOJOBuilder(withPrefix = StringUtils.EMPTY)
public static class Builder {
}
}
Service calling repo seems to be where the exception is thrown
public List<City> getCities(){
return cityRepo.findAll().stream().map(cityEntity -> objectMapper
.convertValue(cityEntity, City.class))
.collect(Collectors.toList());
}
The problem is that Jackson cannot deserialize the object value.
My solution was to add the following annotations to my class:
// constructor with no args
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
// constructor with all args
#AllArgsConstructor
// ignore unknown properties during deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
My class ended up looking like this:
#Getter
#Builder
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyClass {
private boolean flag;
private boolean flag2;
private MyClassA objectA;
private MyClassB objectB;
}
If you want to read more on why should we use #NoArgsConstructor and #AllArgsConstructor together, here is a good answer.
Are You sure You always pass name and description to the class Builder?
I got the same error and In my case I was trying to to use a generated Builder to create an Object but I did not pass all of the arguments, so the generated method was not the one spring was looking for. It was searching the N+1 arguments method, but I was passing only N arguments. In this case it will look for a different method signature that can not find.

Spring request validation

I discover that Spring request validation differs from laravel-validation.
I must validate fields by type and by other constraints (e.g. max length). But Spring throws different exceptions when field cannot be casted to Dto’s field type (thrown by Jackson) or it’s just too long (thrown by jsr-303).
How can I validate JSON request by JSON-schema? I think it’s better solution.
Use javax.validation.constraints on your DTOs. Here is an example from baeldung
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
public class User {
#NotNull(message = "Name cannot be null")
private String name;
#AssertTrue
private boolean working;
#Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int age;
#Email(message = "Email should be valid")
private String email;
// standard setters and getters
}
Okay, I deal with the problem using java-json-tools/JSON-Schema-Validator library.
I have created service with only one method: “validate” which receives JsonNode and name of file with json-schema. If validation fails, validator throws my custom exception, and exception handler renders error message

Spring Boot: Custome #Query with 2 consecutive brackets cause that one pair is ignored

In my JpaRepository I have following #Query:
#Query("SELECT m FROM Msg m WHERE ((m.from = ?1 AND m.to = ?2) OR (m.from = ?2 AND m.to = ?1)) AND m.time = ?3")
Msg find(String firstId, String secondId, long lastAccess);
however in log console is this query logged without the upper brackets and it seems that is executed that way:
SELECT m FROM Msg m WHERE (m.from = ?1 AND m.to = ?2) OR (m.from = ?2 AND m.to = ?1) AND m.time = ?3
So how to properly add multiple consecutive brackets?
I use:
Derby
Spring Boot 1.4.3.RELEASE
I did some small investigation using Spring Boot 1.4.3-RELEASE and 1.5.4-RELEASE with two dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
I have mimic your JpaRespository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
interface PersonRepository extends JpaRepository<Person, Long> {
#Query("SELECT p FROM Person p WHERE ((p.firstName = :firstName AND p.lastName = :lastName) OR (p.firstName = :lastName AND p.lastName = :firstName)) AND p.age = :age")
Person findWithCustomQuery(#Param("firstName") String firstName, #Param("lastName") String lastName, #Param("age") Integer age);
}
Here is what Person class look like:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private Integer age;
}
Here is full source code: https://github.com/wololock/stackoverflow-answers/tree/master/45629734
The SQL query I see that is executed is following:
select person0_.id as id1_0_, person0_.age as age2_0_, person0_.first_name as first_na3_0_, person0_.last_name as last_nam4_0_ from person person0_ where (person0_.first_name=? and person0_.last_name=? or person0_.first_name=? and person0_.last_name=?) and person0_.age=?
As you can see brackets where simplified to
(person0_.first_name=? and person0_.last_name=? or person0_.first_name=? and person0_.last_name=?)
but it is still correct since and operator has higher priority than or operator.
More information needed
I'm eager to help you finding solution to your problem, but I will need more information, e.g.
what database do you use (I used H2 in this example and it's pretty obvious that different SQL dialect may generate different query)
what version of Spring Boot do you use?
what version of Spring Data do you use?

How should I create H2 using SpringBoot?

I'm starting to play with Spring Boot and as part of that I want to create an in memory DB to work with and bootstrap with the application.
Given the config/code below I get no errors in the startup log and can access the application ok, so it does startup (I get template errors about objects not existing), but I don't get any data back from the DAO when calling findAll() (or if I try to call findById(int) ).
So while it seems things are ok (no error in log, log shows it finds the sql to create schema ad attempts to run the data.sql statements) when I try to access data via the DAO I get no Exception, but no data returned.
Any ideas or observations on the code that might be a problem?
I've added the Spring Data / H2 stuff to my pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
Spring DAO:
public interface PersonDao extends CrudRepository<Person, Integer> {
}
DB props in application.properties:
server.contextPath=/
server.port=8080
spring.mvc.view.suffix=.ftl
datasource.mine.jdbcUrl=jdbc:h2:tcp://localhost/mem:clubmanagement
datasource.mine.user=sa
datasource.mine.password=
datasource.mine.poolSize=30
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=DEBUG
spring.jpa.hibernate.ddl-auto=create
My service:
#Service
public class MemberServiceImpl implements MemberService {
#Autowired
PersonDao dao;
#Override
public Optional<ClubMember> getClubMember(int id) {
Person dbPerson = dao.findOne(id);
if(dbPerson == null) {
return Optional.empty();
}
return Optional.of(fromEntity(dbPerson));
}
#Override
public List<ClubMember> allMembers() {
Iterable<Person> people = dao.findAll();
List<ClubMember> members = new ArrayList<>();
people.forEach(person -> {
members.add(fromEntity(person));
});
return members;
}
private ClubMember fromEntity(Person p) {
ClubMember member = new ClubMember();
member.setCurrentGrade(p.getCurrentGrade());
member.setFirstName(p.getFirstName());
member.setLastName(p.getLastName());
member.setAssociationMemberId(p.getAssociationMemberId());
member.setLastGradingDate(p.getLastGradingDate());
return member;
}
}
Schema.sql in resources/ :
create table CLUB
(id int not null, name varchar(60), association_member_id int);
create table PERSON
(
id int not null, grade_id int, first_name varchar(35), last_name varchar(35),
association_membership varchar(12), last_grading_date date
);
create table GRADE
(id int not null, name varchar(20));
In data.sql (again in resources directory):
insert into club (id, name, association_member_id) values (1, 'some club', '123');
insert into person (id, grade_id, first_name, last_name, association_membership, last_grading_date)
values (1, 1, 'name', 'lastname', 'a1234', '2016-03-23');
Entity class I am trying to retrieve (Trying to use Lombock, also new to me, to generate getters/setters):
#Entity
#Table(name = "person")
public #Data class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#JoinColumn(name = "grade_id")
private GRADE currentGrade;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "association_membership")
private String associationMemberId;
#Column(name = "last_grading_date")
#Temporal(value = TemporalType.DATE)
private Date lastGradingDate;
}
you want to add H2 database, but you added HSQLDB, please replace
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
with
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
EDIT
I noticed you have multiple issues in your code:
default schema file name is schema.sql not Schema.sql
names of tables in schema.sql are different than names in data.sql (PERSON vs person)
you used this spring.jpa.hibernate.ddl-auto=create in application.properties (default option), in this case JPA databases schema only will be automatically created (without data creation), so data.sql will not be executed, to fix this issues you can use validate or update option
I will write one simple example how to use H2 database with spring boot and JPA
This is the project structure:
Grade Entity
package com.shijazi;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="GRADE")
public class Grade {
#Id
#GeneratedValue
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Grade(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Grade() {
}
}
GradeRepository.java
package com.shijazi;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface GradeRepository extends JpaRepository<Grade, Integer> {
}
Application.java
#SpringBootApplication
#RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Autowired
private GradeRepository gradeRepo;
#RequestMapping(value="api/test")
public List<Grade> getall()
{
return gradeRepo.findAll();
}
}
application.properties
spring.jpa.hibernate.ddl-auto=validate
schema.sql
create table GRADE (id int not null, name varchar(20));
data.sql
insert into GRADE (id, name) values (2, 'name');
Dependencies in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Now just run application and call this URL: http://localhost:8080/api/test
try to change the spring.jpa.hibernate.ddl-auto and see results
if you activate ddl-auto and have a schema.sql, BOTH of them are executed. But normally schema.sql is executed first. So the ddl-auto throws everything away, which was created by schema.sql and data.sql
After spending some time working through some ideas with #Safwan Hijazi in chat, came to the conclusion that what is happening is that the schema.sql and data.sql were being run but then the schema was recreated depending on the value (or lack of) of the spring.jpa.hibernate.ddl-auto property.
If not specified, spring/hibernate between them ended up recreating an empty schema (default seems to be create-drop for in memory DB).
If set the 'none' then that wouldn't happen and DB as created by the schema and data sql scripts would remain and the application functioned correctly.
See also: CrudRepository not reading data from schema.sql
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Just gives you an spring boot opinionated inclusion to the spring-boot-starter-data-jpa maven file, for the bill of material of all the dependencies. To use any one of the dependency defined in the Dependency management of the spring-boot-starter-data-jpa pom you will have to explicitly declare the dependency in dependency section of your pom file.
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
To get the h2 database up and running with your application, you can specifiy the properties in your application.properties file at src/main/resources/application.properties use:
spring.h2.console.enabled=true
spring.h2.console.path=/h2DB
so when you run your application with spring application starter, you will be able to access the application at http://localhost:8080/h2DB login to the DB and you can verify if the database had the inserts in it or not?
Don't find the data in there then you know where to make a change to keep the data there.

Resources