I am learning spring boot, I am adding Validation for #PostMapping, but somehow it always create a object even with non-valid value.
Hospital.java
public class Hospital {
private Integer id;
#Size(min = 2)
private String name;
#Size(min = 2)
private String city;
public Hospital(Integer id, String name, String city) {
this.id = id;
this.name = name;
this.city = city;
}
Controller
#Autowired
HospitalData data;
...
#PostMapping("/hospital")
public ResponseEntity<Hospital> addHospital(#Valid #RequestBody Hospital hospital){
Hospital newHospital = data.addHospital(hospital);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(newHospital.getId()).toUri();
return ResponseEntity.created(location).build();
}
pom.xml
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
And previously I have added below dependency as I am using Spring 2.3.10 RELEASE, but it doesn't work, so I have added above dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I created a Test Application reproducing the state of your code. As stated under the comments, the code you provided should definitely work. You definitely don't neet to provide a BindingResult to the method. Spring Boot throws a MethodArgumentNotValidException and therefore returns a bad request http status if the validation fails.
I created a project with following content:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
DemoEntity:
public class DemoEntity {
#NotNull
public String name;
#Size(min = 3)
public String greeting;
}
DemoController:
#Controller
public class DemoController {
#PostMapping("/post")
public ResponseEntity<DemoEntity> put(#Valid #RequestBody DemoEntity demoEntity) {
return ResponseEntity.ok(demoEntity);
}
}
Now, that's what happens with my requests:
Name
Greeting
Result
Peter
Mr
400
Stephen
Monsieur
200
Clara
null
200
Jenny
Madamme
200
As you see from the table above, when greeting is null the result is an ok status. If you want to guarantee that the string is at least min characters long and not null, you need to declare this explicitely.
That's, for example, if you want to validate optional fields like a mobile number. You know, it should be n numbers long and contain only numbers, but you don't want to make it mandatory. Like I showed with the greeting above.
You need to inject the BindingResult object as the param immediately following your form object and then manually check if there were any errors.
See https://spring.io/guides/gs/validating-form-input/ Create a Web Controller for a good example of how this should be done in your controller.
Related
I am trying inject a dependency or at least filtrate the ID parameter that come into a RestControler in Spring. I am very new in Spring. How can i be sure that the parameter that comes passed in the API is valid and/or how can i inject its dependency related to Customer Entity?
This is my rest controller CustomerController method
#PatchMapping("/{id}")
public Customer updateCustomer(#PathVariable Long id, #RequestBody Customer customer) {
return customerService.updateCustomer(id, customer);
}
This is the request that at the moment filtrates only the firstname and last name
package com.appsdeveloperblock.app.ws.requests.customer;
import javax.validation.constraints.NotNull;
public class CreateCustomerRequest {
#NotNull
private String firstname;
#NotNull
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
Thank you!
You need the Bean Validation API (which you probably already have) and it's reference implementation (e.g. hibernate-validator). Check here Java Bean Validation Basics
Summarizing
Add the respective dependencies to your pom.xml (or gradle):
<dependencies>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.1.2.Final</version>
</dependency>
</dependencies>
Use #Valid annotation on your Customer entity to have the payload validated automatically:
#PatchMapping("/{id}")
public Customer updateCustomer(#PathVariable Long id, #RequestBody #Valid Customer customer) {
return customerService.updateCustomer(id, customer);
}
You can decorate the fields of your Customer or CreateCustomerRequest class with further annotations, e.g. #Size, #Max, #Email etc. Check the tutorial for more information.
In a Spring Boot application I want to test (JUnit 5) the persistence layer with enabled auditing (#EnableJpaAuditing).
I use Liquibase to setup a H2 db and Hibernate as the JPA implementation.
#Configuration
//#EnableTransactionManagement
#EnableJpaAuditing
//#EnableJpaRepositories
public class MyPersistenceConfig {
}
My entity has the following fields:
#CreatedDate
#Column(name = "CREATED_AT", updatable = false)
private Instant createdAt;
#CreatedBy
#Column(name = "CREATED_BY", updatable = false)
private String createdBy;
#CreatedDate
#Column(name = "LAST_MODIFIED_AT")
private Instant lastModifiedAt;
#CreatedBy
#Column(name = "LAST_MODIFIED_BY")
private String lastModifiedBy;
I have the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<scope>runtime</scope>
<!--<scope>test</scope>-->
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
I tried several combinations of annotations:
#SpringBootTest //(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//#DataJpaTest
#ContextConfiguration(classes = MyPersistenceConfig.class)
#EnableAutoConfiguration
//#SecurityTestExecutionListeners
//#Import(SpringBootWebSecurityConfiguration.class)
#WithMockUser(username = "test", password = "test", roles = "USER")
#ExtendWith(SpringExtension.class)
class MyRepositoryTest {
#Autowired
private MyRepository testee;
...
}
But whatever I try, either the repository is null (autowiring) or I get an exception when inserting an entry:
NULL not allowed for column "CREATED_BY"; SQL statement:
I guess I need a SecurityContext (which isn't available for autowiring currently).
What is the easiest way to provide a mock SecurityContext that works with auditing and #WithMockUser?
This is an old question but for those who may stumble upon it trying to get Spring Data auditing to work in their integration tests this may help. The auditing functionality requires an AuditingAware bean to get the current user. In DataJpaTest this seems to be missing. One way to make it available is adding a #Bean configuration to your test.
#RunWith(SpringRunner.class)
#DataJpaTest
#Import({DatabaseIntegrationTest.TestConfig.class})
#WithMockUser
class DatabaseIntegrationTest {
#TestConfiguration
static class TestConfig {
#Bean
public AuditorAware<String> auditorAware() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication().getName());
}
}
}
#Before
public void setup() {
User user = userService.findByEmail("umanking#gmail.com").get();
SecurityContextHolder.getContext().setAuthentication(new Authentication() {
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public Object getCredentials() {
return user.getPassword();
}
#Override
public Object getDetails() {
return user;
}
#Override
public Object getPrincipal() {
return null;
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return user.getName();
}
});
}
maybe you can use #Before annotation
Is it possible to implement JSR303 Bean validation with a plain spring-boot-starter project, Below are the source code I have failed to get working.
Does it require a Validation Bean to be configured if so it I can get it to do the validation using afterPropertiesSet but how to get it to work even when the properties are modified after the instance is created and how to get it to work for non Spring beans without using the Validator instance(to get the validation happen automatically whenever the annotations are encountered in a class)
pom.xml snippet
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>jsr303</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
....
</project>
Source code
#Service
public class ProfileService {
public void createProfile( #Valid #NotEmpty String name, #Valid #NotEmpty String email) {
new Profile(
new EmailAddress(
"somasundarams#newemail.com"
),
"Somasundaram S"
);
}
}
Profile.java
public class Profile {
private EmailAddress emailAddress;
private String name;
public Profile(#Valid #NotNull EmailAddress emailAddress, #Valid #NotEmpty String name) {
this.emailAddress = emailAddress;
this.name = name;
}
.....
}
EmailAddress.java
public class EmailAddress {
private String email;
public EmailAddress(#Email String email) {
this.email = email;
}
.....
}
Jsr303Application.java
#SpringBootApplication
public class Jsr303Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Jsr303Application.class, args);
ProfileService profileService = context.getBean(ProfileService.class);
profileService.createProfile(null, "Somasundaram S");
}
}
I am trying to create a connection for my spring project, soemthing very simple.
I want to create the configuration fields on my application.properties. I tried with the spring.datasource and I dont get any error, but still, I dont get any info, just null pointers... I think the connections have not been made properly.
Here is part of my pom, with this dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
And my properties:
server.port=80
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/database
jdbc.username=user
jdbc.password=password
I dont get any error, but when I query the database, I always get a null pointer, which makes me think is not working properly, any idea how to configure this?
Thanks
EDIT:
My configuration class
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
The controller
#Autowired
private MyService myService;
#RequestMapping("/")
public String index() {
User myUser = myService.findUserById(1L);
System.out.println(myUser);
return myUser.getFirstName();
}
My Service Implementation
#Service
public class MyServiceImpl implements MyService {
#Autowired
UserRepository userRepository;
public User findUserById(Long id){
return userRepository.findOne(id);
};
}
My Entity
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
And my repository
public interface UserRepository extends CrudRepository<User, Long> {
//User findOne(Long id); this is a default method
}
THE ERROR:
I always get null pointer, even if data exist... and if I change the database password for a wrong one, I dont get any error.. like if it would be ok.
1) Please remove below dependency from pom, because of it might let SpringBoot use h2 instead of mysql, which cause you don't see any error even with wrong mysql password.
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
2) In application.properties, please use:
spring.datasource.url=jdbc:mysql://localhost:3306/database
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
These property names can be found from SpringBoot document: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html:
The correct data source url is:
jdbc:mysql://HostName:Port(3306 is the default)/DBName
You are missing the #Repository annotation on your Repository. Add that and see if that works for you.
If not, 1. share the complete stack trace that you are getting.
2. Also, are you able to reach to the controller endpoint?
I noticed your entity does not implements Serializable interface.
If this is not the issue please take a look at the spring boot example from there github repository for reference.
https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-data-jpa/
I have studied many, many answers and I still can't see what I'm doing wrong.
I have a REST service controller which takes a bean as a parameter and I would like to validate that the fields on the bean are properly set.
The following is (a portion of) my configuration object.
#Configuration
#ComponentScan({...})
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Bean
public Validator validator() {
LocalValidatorFactoryBean validatorBean = new LocalValidatorFactoryBean();
return validatorBean;
}
#Override
public Validator getValidator() {
return validator();
}
}
I can't tell if those are necessary, but I can tell they're never called, so maybe they're in the wrong place?
The following is (a portion of) my pom.xml:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>5.1.1.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
I don't think el is necessary for what I'm doing, but I included it in case it made a difference.
Here is (a portion of) my bean class that needs to be validated:
public class ContactInfo {
#NotNull
#Size(min=1, max=50)
private String phone;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
And finally, this is (a portion of) the Rest Controller:
#RestController
#RequestMapping("/contact")
public class ContactController {
private static final String USER_ID_ATTRIBUTE = "userId";
#Autowired
private Validator validator;
#Autowired
#Qualifier("contactService")
private ContactService service;
#RequestMapping("/submit")
public void submitContact(
#Valid #RequestBody ContactInfo formData, BindingResult result) throws Exception {
assert(validator != null);
if(result.hasErrors()) {
throw new Exception("I don't like it one bit.");
}
service.submitContact(formData);
}
}
In the debugger, I can see that the validator passed in is an OptionalValidatorFactoryBean, and result.hasErrors() is always false.
The JSON passed in to the controller includes this:
{ "phone":"" }
I think that should trigger a validation error because it doesn't meet the minimum size.