Spring Data Rest - validate Bean before save - spring-boot

I've been searching for the simplest and best way to validate my entities before they are created/updated and after much googling, I couldn't find a clean/modern one.
Ideally, I would have loved to be able to use #Valid as follows:
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Slf4j
#Validated
#Component
#RepositoryEventHandler
public class CustomerEventHandler {
// Triggered for POST
#HandleBeforeCreate
public void onBeforeCreate(#Valid Customer entity) {
log.info("Saving new entity {}", entity);
}
// Triggered for PUT / PATCH
#HandleBeforeSave
public void onBeforeSave(#Valid Customer entity) {
log.info("Saving new entity {}", entity);
}
}
The Customer entity being:
import javax.validation.constraints.NotBlank;
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "customer")
public class Customer {
#NotBlank
private String firstname;
}
But it doesn't seem to work.
What's the modern, easy way to validate entities in Spring Data REST?
Note: I'm using Spring Boot

I checked your pom.xml in linked GitHub project. You have just a dependency to validation annotations, but the proper way with Spring Boot is to use the spring-boot-starter-validation Dependency. The Spring Boot Starter Dependencies add the "magic" to your project, which triggers automatically the validation based on your annotations.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I my German blog I have written an article about this topic:
https://agile-coding.blogspot.com/2020/11/validation-with-spring.html

I want to suggest a few best practise that every developer, who starting as junior/beginner should be know. Don't put any Validation annotation in Entities, using them in DTO/Resource classes. and the best way to practise validation is that you can handler MethodArgumentNotValidation exception in your own Spring Boot of Exception Handler class annotated that class #RestControllerAdvice and create your own #interface annotation instead of using more validation annotation.

Related

Unable to get table autocreated in spring module integration test

I have a parameters module in my project which other modules are dependent on. I'd like to write instegration tests for this module in separate manner. This module is a spring based one and has some db related logic, though db migration scripts are not available in this module since it is something that is out of its area of resposibility. By the way in-memory H2 instance is used for testing purposes.
What I'm trying to achieve is to make spring/hibernate create DB tables based on single #Entity class present in this module, it is called ParameterEntity.
It is defined like this:
#Entity
#Table(name = "SYSTEM_PARAMETERS")
public class ParameterEntity {
#Id
#Column(name = "id")
private String id;
#Column(name = "value")
private String value;
// accessors go here...
}
In my application-test.yml file I provide the following props:
spring:
jpa:
hibernate:
ddl-auto: create
database: h2
show-sql: true
And define integration test class like this:
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties
#EntityScan("path.to.package.where.parameter.entity.resides")
#ConstextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
#ActiveProfiles("test")
#TestInstance(Lifecycle.PER_CLASS)
public class ParametersIntegrationTest {
#Autowired
private ParameterWarden warden;
#BeforeEach
public void setUp() {
warden.initialize();
}
#Configuration
#ComponentScan("base.module.package")
static class TestConfiguration {
// here comes some test beans which will be used in testing purposes only
}
}
In #BeforeEach method ParameterWarden class calls repository class which in turn make some calls to database to retrieve parameter entities from database and these calls fail because SYSTEM_PARAMETERS is missing.
Could anyone, please, let me know what am I missing here and how can I make spring or hibernate create table based on the entity present in my project. Even the place where I can debug this would be nice to know.
It seems like I need another magical thing that will trigger this functionality but I was unable to figure out what exactly I need.
Any help is really appreciated, thank you very much for your time!
p.s. I can not use #SpringBootTest annotation since this module uses only some spring features and is not a spring boot application itself. It is used as a dependecy in another spring boot applications.
Can you still use #DataJpaTest?
package com.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#DataJpaTest
#ContextConfiguration(classes = TestEntityRepository.class)
#TestPropertySource(properties = {
"spring.jpa.hibernate.ddl-auto=create",
"spring.datasource.platform=h2",
"spring.jpa.show-sql=true"
})
#EnableAutoConfiguration
public class IntegrationTest {
#Autowired
TestEntityRepository testEntityRepository;
#Test
void testCreateRead() {
var saved = testEntityRepository.save(new TestEntity("test"));
Assertions.assertNotNull(saved);
var read = testEntityRepository.findById(saved.getId());
Assertions.assertNotNull(read);
}
}
Where repository and entity in the com.test package are:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface TestEntityRepository extends CrudRepository<TestEntity, Long> { }
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Data
#NoArgsConstructor
public class TestEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private Long id;
#Column
private String attr;
public TestEntity(String attr) {
this.attr = attr;
}
}
Dependecies used (through dependency management and Spring Boot BOM of version 2.3.4.RELEASE):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
Alternatively, if you want to fully decouple tests from Spring you can use org.hibernate.tool.hbm2ddl.SchemaExport in some utility logic since that's what's really executing under the hood. I use this approach since my project requires some extra steps to setup the database, however, it might be too complicated for some use cases.

Javax validation not working on boot rest controller parameter

I have a Pojo (annotated with Lombok if it makes any difference) and a RestController in a Spring Boot app. The pojo method parameter is annotated with #Valid yet there is no validation applied and even if I add a BindingResult second parameter, it never has any error. I'm testing this with Swagger UI, posting a JSON, but I don't see why that would make any differnce... Did I miss something obvious?
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Tolerate;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.NotBlank;
#Data
#EqualsAndHashCode(callSuper = true)
#Builder
public class MyPojo extends GenericDocument {
#NotBlank
private String name;
#Tolerate
public MyPojo() {
}
}
import javax.validation.Valid;
#RestController
#RequestMapping(value = "/myUrl", produces = APPLICATION_JSON_VALUE)
public class MyController {
#PostMapping
public ResponseEntity<MyPojo> createNew(#RequestBody #Valid MyPojo pojo){...
}
}
Solution
Adding this dependency was enough:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
It is strange that adding javax.validation:validation-api and org.hibernate:hibernate-validator was not enough despite the documentation
You should try adding Spring’s #Validated annotation to the controller at class level to tell Spring to evaluate the constraint annotations on method parameters.
Example:
#Slf4j
#Validated
#RestController
#RequestMapping(value = "/myUrl", produces = APPLICATION_JSON_VALUE)
public class MyController {
#PostMapping
public ResponseEntity<MyPojo> createNew(#RequestBody #Valid MyPojo pojo){...
}
}
This org.springframework.validation.annotation.Validated annotation can be applied at both class level and method or parameter level.
Per the documentation, which is not entirely clear, you need to have a JSR-303 validation provider such as Hibernate Validator (docs here) on your classpath so Spring can detect and use it.
It also seems in some cases you may need the Spring Boot Starter Validation dependency (spring-boot-starter-validation) on your classpath as well. I would suggest trying that to see if it helps. (Strangely, though, I don't see that detailed in the Spring docs - just briefly mentioned here.)
Or you could maybe implement the Validator interface as per here, but not sure.

#CreatedBy and #LastModifiedDate are no longer working with ZonedDateTime?

i have updated an old spring boot 1.5.3 project to spring boot 2.0.0. RELEASE.
I have an auditing entity with two fields of type ZonedDateTime annotated with #CreatedBy and #LastModifiedDate.
In the previous versions everything was working fine. However, with the new update, upon saving the entity in the repository i get an error i.e
createdDate=<null>
lastModifiedDate=<null>
]! Supported types are [org.joda.time.DateTime, org.joda.time.LocalDateTime, java.util.Date, java.lang.Long, long]
I checked the AnnotationAuditingMetaData, and i found noting related to ZonedDateTime.
There is also this issue in https://jira.spring.io/browse/DATAJPA-1242, I believe it's related.
My question is what am i doing wrong here, does spring stopped the support or am i doing something wrong?
I had the same problem and could solve it adding a dateTimeProviderRef to #EnableJpaAuditing.
Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import java.time.ZonedDateTime;
import java.util.Optional;
#Configuration
#EnableJpaAuditing(dateTimeProviderRef = "auditingDateTimeProvider")
public class PersistenceConfig {
#Bean // Makes ZonedDateTime compatible with auditing fields
public DateTimeProvider auditingDateTimeProvider() {
return () -> Optional.of(ZonedDateTime.now());
}
}
Kotlin
#Configuration
#EnableJpaAuditing(dateTimeProviderRef = "auditingDateTimeProvider")
class PersistenceConfig {
#Bean // Makes ZonedDateTime compatible with auditing fields
fun auditingDateTimeProvider()= DateTimeProvider { of(ZonedDateTime.now()) }
}
Old answer was mistaken, but I can't delete it because it is accepted. Please scroll to other answers.
You also have to put this annotation
#EntityListeners(AuditingEntityListener.class)
on the class where you use the #CreatedDate.

Spring data repository not found at compile time

I am trying to use Spring data and repositories in a Spring Boot application, but I have an error when compiling the project.
Here is my Entity :
package fr.investstore.model;
import javax.persistence.Id;
...
#Entity
public class CrowdOperation {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
public Long id;
#Enumerated(EnumType.STRING)
public RepaymentType repaymentType;
...
}
And the corresponding Repository:
package fr.investstore.repositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import fr.investstore.model.CrowdOperation;
public interface CrowdOperationRepository extends CrudRepository<CrowdOperation, Long> {
}
I use it in a WS controller, generating a repository through the Autowired annotation:
package fr.investstore.ws;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
...
#Controller
#EnableAutoConfiguration
public class SampleController {
#Autowired
private CrowdOperationRepository crowdOperationRepository;
#RequestMapping(path = "/", method = RequestMethod.GET)
#ResponseBody
public String getOperations(#RequestParam(required=true, defaultValue="Stranger") String name) {
crowdOperationRepository.save(new CrowdOperation());
return "Hello " + name;
}
}
And the code of the application:
package fr.investstore;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import fr.investstore.ws.SampleController;
#SpringBootApplication
public class InvestStoreApplication {
public static void main(String[] args) {
SpringApplication.run(SampleController.class, args);
}
}
But when compiling the project I get:
APPLICATION FAILED TO START
Description: Field crowdOperationRepository in
fr.investstore.ws.SampleController required a bean of type
'fr.investstore.repositories.CrowdOperationRepository' that could not
be found.
Action: Consider defining a bean of type
'fr.investstore.repositories.CrowdOperationRepository' in your
configuration.
Woudn't Spring automatically generate a bean for the repository through the interface?
How can I resolve this?
EDIT: I also tried to put the Repository annotation (from org.springframework.stereotype.Repository) onto CrowdOperationRepository, but I got the same error
While creating a spring-boot application, we need to keep some point in our mind like
Always keep main class (class with `#SpringBootApplication annotation) on the top level package and other classes should lie under sub-packages.
Always mark your bean classes with proper annotation e.g. all repositories should be marked by #Repository annotation, all service implementation classes should be marked with #Service, other component classes should be marked by #Component, class which defines our beans should be marked as #Configuration
Enable the feature which you are using e.g. #EnableJpaRepositories, #EnableTransactionManagement, #EnableJpaAuditing, these annotations also provides functionality which let us define which package spring needs to scan.
So in your case, you need to mark InvestStoreApplication class with #EnableJpaRepositories annotation and CrowdOperationRepository with #Repository.
you have to tell your spring boot application to load JPA repositories.
copy this one to your application class
it will auto-scan your JPA repository and load it in your spring container even if you do not define your interface with #Repository it will wire that bean in your dependent class.
#EnableJpaRepositories(basePackages = { "fr.investstore.repositories" })
Thank to #JBNizet for his comment, that made it working.
I create this answer since he did not:
Replace SpringApplication.run(SampleController.class, args); with SpringApplication.run(InvestStoreApplication.class, args);. And remove the useless #EnableAutoConfiguration on your controller.
Annotating your entity class as shown as spring hint below to allow spring get a valid repository bean
Spring Data JPA - Could not safely identify store assignment for repository candidate interface com.xxxxx.xxxxRepository.
If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: javax.persistence.Entity, javax.persistence.MappedSuperclass (preferred),
or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository.
2022-05-06 12:32:12.623 [ restartedMain] INFO [.RepositoryConfigurationDelegate:201 ] - Finished Spring Data repository scanning in 3 ms. Found 0 JPA repository interfaces.

Implementation of DAO vs JPA

I would like to know the difference between JPA and Hibernate. I have read the very interesting question posted by #Anthony with interest but still I do not understand the full picture.
I have implemented my application in Spring MVC and Hibernate (see below). My DAOs are an implementation of services which have been built using HQL queries.
#Service("messagesService")
public class MessagesService
{
private MessagesDAO messagesDAO;
#Autowired
public void setMessagesDAO(MessagesDAO messagesDAO)
{
this.messagesDAO = messagesDAO;
}
public List<Message> getAllMessages()
{
return messagesDAO.getAllMessages();
}
...
--------
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
#Repository
#Transactional
#Component("messagesDAO")
public class MessagesDAO
{
#Autowired
private SessionFactory sessionFactory;
public Session session()
{
return sessionFactory.getCurrentSession();
}
#SuppressWarnings("unchecked")
public List<Message> getAllMessages()
{
Criteria crit = session().createCriteria(Message.class);
crit.createAlias("usernameSender", "u").add(Restrictions.eq("u.enabled",true));
return crit.list();
}
...
I really like the statement "JPA is the dance, Hibernate is the dancer." but in my specific case I do not fully get why my example is not JPA. MessageService is the dance and MessagesDAO the dancer(Implementation).
As #Kevin states:
Think of JPA as the guidelines that must be followed or an interface,
while Hibernate's JPA implementation is code that meets the API as
defined by the JPA specification and provides the under the hood
functionality.
I know I have not defined my service as an interface but this still lets me think that my code complies with the JPA specification requirements.
Now a concern arises
What is the difference between my example and the following from the pet clinic example
package org.springframework.samples.petclinic.repository;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.samples.petclinic.model.BaseEntity;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType;
public interface PetRepository {
List<PetType> findPetTypes() throws DataAccessException;
-------
#Repository
public class JpaPetRepositoryImpl implements PetRepository {
#PersistenceContext
private EntityManager em;
#Override
#SuppressWarnings("unchecked")
public List<PetType> findPetTypes() {
return this.em.createQuery("SELECT ptype FROM PetType ptype ORDER BY ptype.name").getResultList();
}
The reason why I am asking all this questions is because I am using MySql in my application and I am thinking to change it in the future.
Therefore, I am trying to build my implementation layer to avoid any problem later on.
I was looking at nosql options and I discovered spring data jpa for the integration layer. I then started to learn a bit more about JPA and DAOs and the questions above suddenly arose
If I implement spring data jpa, can I use MySql for the moment and change it later to another database (Cassandra, MongoDb)?
What is the difference between Spring data JPA and Spring Data Mongodb
(The first is the specification and the second one the implementation?)
Thank you for your help
To keep the terminology: In your DAO you can't change your dancing partner.
Why? Because you are referencing Hibernate explicitly. If you would like to change the dancer some day you would have to change your whole implementation. Thats why you usually use only classes of the dance - JPA or Spring Data in your case.

Resources