Spring data and hibernate - model validations - exception translation - spring

I use spring-data and hibernate. Now I would like to apply some validation to my model. In most cases I would like to apply simple validation like null-checking etc. But in some cases I would like to apply more strict validation, such as email-validation. I found very useful feature in Hibernate validator - the #Email annotation. It works very well but here is the problem:
If i try to save a model with null value, then the following exception is thrown:
org.springframework.dao.DataIntegrityViolationException
But if I try to save a model with non-null but non-email value (let's say asdfgh), then the following exception is thrown:
javax.validation.ConstraintViolationException
I would love to see only one type of exception in both cases, because in both cases the model didn't pass the validation and I would like just to worry about only one exception type in my exception-handling code.
I tried to add PersistenceExceptionTranslationPostProcessor to my bean configuration, but it looks like it does not change anything.
Do you have an idea how to "unify" this exceptions?
Model:
#Entity
public class ValidationModel {
...
#Email
#Column(nullable = false)
private String email;
...
}
Repository:
public interface ValidationModelRepository extends JpaRepository<ValidationModel, Long> {
}

#Column(nullable = false) is not a validation check. It's a JPA constraint.
To validate that a value is not null, use #NotNull.

Related

Spring MVC: how to avoid duplicating validation at controller and entity

Let's say I have a "Person" #Entity managed via JPA, which has a series of validations applied at entity-level (#NotBlank, #NotNull etc).
#Entity
public class Person {
#NotBlank
private String name;
#Email
private String email;
...
}
For various reasons, we shouldn't directly use an entity as the controller method argument, but rather create a custom "form" (taking the example from https://spring.io/guides/gs/validating-form-input/)
#PostMapping("/person/save")
public String savePerson(#Valid PersonForm personForm, BindingResult bindingResult) {
// map fields to Person entity individually and save
...
}
But now, to make use of Spring's built-in form validation / BindingResult, it appears I have to duplicate all my validation logic on both the Person and PersonForm classes. I don't just want to define them on PersonForm, because there might be other routes in the application to update a Person.
Ideally there would be some way that Spring could lift up the validation constraints on the #Entity and apply them to the form (e.g. if the properties had the same name).
Am I missing something here with validation?

How to use #Postconstruct in order to load some data

currently, I have an import.sql with which I import some test data in my database. Now, I want to bring it to our production system and what I read so far is that I should not use the import.sql in production.
Therefore, I thought I can create something with #Postconstruct.
I, therefore, created in the main application class something like that:
#Autowired
ICreateUserAtStartup repo;
#PostConstruct
public void initIt() throws Exception {
Rolle r = new Rolle();
r.setBezeichnung("xxx");
r.setId(1L);
r.setCreatedAt(new Date(2019, 01, 14));
repo.insertRolle(1L, "xxx");
}
In an seperate file I created the following interface:
#Repository
public interface ICreateUserAtStartup {
#Modifying
#Query("insert into benutzer(id, created_at, anzeigename,
benutzername, dienstnummer, active, passwort) SELECT :id,
:created_At, :anzeigename, :benutzername, :dienstnummer, :active,
:passwort")
void insertBenutzer(#Param("id") Long id, #Param("created_at")
String created_at, #Param("anzeigename") String anzeigename,
String benutzername, String dienstnummer, Boolean active, String password);
#Modifying
#Query("insert into rolle(id, bezeichnung) SELECT (:id,
:bezeichnung)")
void insertRolle(#Param("id") Long id, #Param("bezeichnung")
String bezeichnung);
}
However, as soon as I try to autowire repo in my main class, I always get the following exception:
No qualifying bean of type 'x.y.z.repository.ICreateUserAtStartup' available
Why don't you just use a specific migration tool for this purpose like Flyway or Liquibase?
https://flywaydb.org/
https://www.liquibase.org/
The reason why it cannot be autowired is that you haven't implemented that interface and created a bean from the implementation. Of course you might think that if you just create an interface and annotate it with #Repository, it will work out of the box but that's not the case.
If you want to use Spring Data for your repository layer, you'll need an entity and you need to extend at least CrudRepository.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories

How to generate a value for a column in a JPA entity, while querying the database?

I have an entity that looks like this:
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"slug"})})
public class BlogPost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String title;
#Column
private String slug;
}
I would like to generate the value of slug before persisting by doing the following:
Transforming the title from e.g. Blog Post Title to blog-post-title
Making sure that blog-post-title is unique in table BlogPost, and if it's not unique, I want to append some suffix to the title so it becomes e.g. blog-post-title-2
Since I need this on a lot of entities, my original idea was to create an EntityListener which would do this at #PrePersist. However, documentation generally states that I should not call EntityMan­ager or Query methods and should not access any other entity objects from lifecycle callbacks. I need to do that in order to make sure that my generated slug is indeed unique.
I tried to be cheeky, but it is indeed very hard to autowire a repository into an EntityListener with Spring anyway.
How should I best tackle this problem?
Thanks!
Both OndrejM and MirMasej are definitely right that generating a slug would not be something to be done in an Entity. I was hoping EntityListeners could be a little "smarter", but that's not an option.
What I ended up doing is using aspects to accomplish what I wanted. Instead of "hooking" into entities, I am rather hooking into save method of CrudRepository.
First, I created an annotation so I can recognize which field needs to be sluggified:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Slug {
/**
* The string slug is generated from
*/
String source() default "title";
/**
* Strategy for generating a slug
*/
Class strategy() default DefaultSlugGenerationStrategy.class;
}
Then, I created an aspect which is something like this:
#Aspect
#Component
public class SlugAspect {
... // Removed some code for bravity
#Before("execution(* org.springframework.data.repository.CrudRepository+.save(*))")
public void onRepoSave(JoinPoint joinPoint) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Object entity = joinPoint.getArgs()[0];
for (Field field: entity.getClass().getDeclaredFields()) {
Slug annotation = field.getAnnotation(Slug.class);
if (annotation != null) {
CrudRepository repository = (CrudRepository) joinPoint.getTarget();
Long count = 0L;
SlugGenerationStrategy generator = (SlugGenerationStrategy)annotation.strategy().newInstance();
String slug = generator.generateSlug(slugOrigin(entity));
if (id(entity) != null) {
Method method = repository.getClass().getMethod("countBySlugAndIdNot", String.class, Long.class);
count = (Long)method.invoke(repository, slug, id(entity));
} else {
Method method = repository.getClass().getMethod("countBySlug", String.class);
count = (Long)method.invoke(repository, slug);
}
// If count is zero, use the generated slug, or generate an incremented slug if count > 0 and then set it like so:
setSlug(entity, slug);
}
}
}
}
I put the code on github (though it's still just a proof of concept) if anyone is interested at: https://github.com/cabrilo/jpa-slug
It relies on having CrudRepository from Spring Data and having these two methods on a repo: countBySlug and countBySlugAndIdNot.
Thanks again for the answers.
The most straightforward solutions seems to make a check before setting the value of the title. It would mean however that the logic of calculating the slug would be outside of the entity and both would come from outside.
You have to think of an entity as a plain object without any connection to the database - this is the idea of ORM. However, you may pass a reference to EntityManager or DAO as an additional argument to a setter method, or somehow inject a reference to it. Then you may call a query directly from the setter method. The drawback of this solution is that you need to always provide EntityManager, either when you set title, or when you create/load the entity.
This is the best object oriented way of solving this problem.

Spring validate nested object with condition on parent

What I want to achieve might be a bit weird but sadly I cannot change that easily. I have a DTO that has a nested resource. The nested resource gets validated and the validation it gets differs based on an attribute of its parent.
class rootDto {
#NotEmpty
private String type;
#Valid
private AddressDto address;
// Other attributes follow ...
}
Now AddressDto looks like the following:
class AddressDto {
#NotEmpty(/* ONLY IF rootDto.type IS 'xxx' */)
private String name;
#NotEmpty(/* ONLY IF rootDto.type IS 'yyy' */)
private String street;
// Other attributes follow ...
}
I read about class level contrains (example answer) but I don't think that this is what I want. I could always create a Validator for the rootDto and inside that have the conditional validations for the AddressDto but that would be ugly and will grow way too much since AddressDto is not the only nested resource I have that requires such validation.
So my main question is, can I somehow expose to the nested resource attributes from its parent in order to use them for the validation? Is there an alternative I have not found/thought?
If you are using Hibernate Validator as Bean Validation provider and are happy to use a non standard feature, you can use Hibernate Validator's GroupSequenceProvider SPI - http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html_single/#__literal_groupsequenceprovider_literal
The idea would be to work with validation groups which you programmatically activate by using a GroupSequenceProvider on your RootDto.

Jpa + Spring - automatically setting transient field value after read from DB

what's the best solution to set a value for a field marked #Transient after the entity has been read from the data source?
I'm using EclipseLink and I'm trying the DescriptorEventAdapter with his postBuild event solution because I need also to get the default value using a Spring bean (obviuosly using DI), but I would know if there is any simpler solution that I'm missing.
Thanks in advance
Here's the simple approach if you're using a repository or DAO:
#Repository
class YourRepository {
#Autowired
private Bean bean;
#PersistenceContext
private EntityManager entityManager;
#Transactional(readOnly = true)
public YourEntity find(..) {
YourEntity entity = lookupUsingEntityManager();
entity.transientField = bean.getDefaultValue();
return entity;
}
}
Here's another approach if you are using active record -style entities:
#Entity
class YourEntity {
#Transient
public Object field;
#PostLoad
public void populateField() {
field = new BeanHolder().bean.getDefaultValueForField();
}
#Configurable
private static class BeanHolder {
#Autowired private Bean bean;
}
}
Mind the semi-pseudo-code. Note that the latter approach works only if you use compile- or load-time AspectJ weaving with <context:spring-configured />.
You got entity which has transient field and the value is always taken from service using DI?
What is the purpose of the field? It's used for some calculation within any entity method?
Such calculation should probably use service's method to obtain the value.
As value from any service is used, I'm not sure whether such calculation (method) belong into entity.
Note that entity and service has completely different lifecycle. The value is changing in the time so it does not make the sense to supply the value in entity's factory at the beginning of it's life?

Resources