Unique Field Validation Using Hibernate and Spring
There are quite a few approaches to validating whether or not a given value already exists in a data store or not. In this article, I will show my solution to this problem by using Hibernate Validator in combination with Spring Framework. As you will see, the latter is solely used for injecting a dependency into my custom validator, so using the validator without Spring Framework should not be too much work.
Other Approaches
Most other solutions that I have found use one of the following approaches to implement a unique constraint validator:
- Inject an EntityManager into the custom validator, or alternatively a SessionFactory
- Inject a repository/DAO into the custom validator
- Inject a specific service into the custom validator
- A solution that does not use a custom validator and thereby does not conform with JSR 303
Personally, I do not fancy using an EntityManager or SessionFactory directly within a validator, because it violates the architecture of my application (MVC + service layer + repository layer); I want all of my business logic to reside within services, hence why checking whether or not a given value exists in my data store should be done within a service. The same applies for using a repository or DAO (Data Access Object) directly within a validator, as this approach also bypasses the service layer. This may not be a problem for you depending on your application’s architecture, but unless you are dealing with a legacy system that is difficult or time consuming to change the architecture of, then you should strongly consider implementing a service layer.
It is also common to inject a specific service instance into a custom validator. This is actually a good solution, but in my case I wanted a solution that was more generic such that I would not have to implement a new validator for each and every time I wanted to validate the uniqueness of a value. Solutions that do not use bean validation (JSR 303) did also not seem feasible because I want to be able to validate a bean with the @Valid annotation before a parameter in a Spring MVC controller action.
My Solution
To overcome these shortcomings, I set out to implement a different approach. Truth be told, my approach has a few shortcomings as well, but I will get back to that. First, let me briefly explain how my solution works.
The core concept of my solution is to inject a service into a custom validator. Where it differs from other solutions that I have seen is that the injected service is dynamic such that the validator can be reused in different contexts (for different fields). This is done by requiring the service to be of type FieldValueExists, which is just a custom interface that I created. You could name this interface anything you want.
public interface FieldValueExists {
/**
* Checks whether or not a given value exists for a given field
*
* @param value The value to check for
* @param fieldName The name of the field for which to check if the value exists
* @return True if the value exists for the field; false otherwise
* @throws UnsupportedOperationException
*/
boolean fieldValueExists(Object value, String fieldName) throws UnsupportedOperationException;
}
I will explain the semantics of this interface later, after seeing how it is used within the custom Hibernate validator. But first we need an annotation that we can use within classes to apply the unique constraint.
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueValidator.class)
@Documented
public @interface Unique {
String message() default "{unique.value.violation}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends FieldValueExists> service();
String serviceQualifier() default "";
String fieldName();
}
If you are not familiar with the above, then I suggest that you read this part of the Hibernate documentation. The target can be either a method, a field or an annotation, where the latter allows this annotation to be grouped with other constraints in a new annotation. With the @Constraint annotation, we define which class that implements the constraint – in this case I decided to name the class UniqueValidator. We define a required service attribute, which is any class that implements our FieldValueExists interface. Furthermore, we require a fieldName attribute to be passed, which is simply the name of the field for which we want to check if a given value exists. I will explain why this attribute is needed in a moment, as well as the serviceQualifier attribute. Please note that the message attribute (which is the error message if the constraint’s isValid method returns false) is a message key, because we have a string enclosed within curly brackets. Therefore you must either define this key within your message source (see here for details), or remove the brackets and provide an error message directly within the annotation. The latter of course means that you then cannot translate the message.
Now, let us have a look at the custom constraint implementation.
public class UniqueValidator implements ConstraintValidator<Unique, Object> {
@Autowired
private ApplicationContext applicationContext;
private FieldValueExists service;
private String fieldName;
@Override
public void initialize(Unique unique) {
Class<? extends FieldValueExists> clazz = unique.service();
this.fieldName = unique.fieldName();
String serviceQualifier = unique.serviceQualifier();
if (!serviceQualifier.equals("")) {
this.service = this.applicationContext.getBean(serviceQualifier, clazz);
} else {
this.service = this.applicationContext.getBean(clazz);
}
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
return !this.service.fieldValueExists(o, this.fieldName);
}
}
So the validator has three fields; the application context, the service that is to be used for the validation, and the name of the field. First the initialize method is invoked with the annotation as an argument, from which we can get its attribute values. First we get the service argument, which is the type of the class that we will use for the validation, such as UserService. This class is used to fetch a bean of that type from the application context that we have let Spring Framework autowire into our custom validator. If there are more than one classes of the type, then one can use the optional serviceQualifier argument, which is the qualifier for the bean that we want to fetch. This could be the case, for instance, if the type is UserService (interface), and we have the following implementations of this interface: UserServiceOneImpl and UserServiceTwoImpl.
Now that our service bean has been fetched from the application context, we can use it from within the isValid method. Because our service is declared with the static type of an interface, we do not need to know the dynamic type of the bean within our validator class. This is essentially what makes this solution dynamic, as it can therefore be used in various contexts.
Dependencies can be injected into the custom Hibernate validator because by default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This allows custom ConstraintValidators to benefit from dependency injection like any other Spring bean. This is documentered here (go to section 5.7.2.2 Configuring Custom Constraints).
Usage
To use the validator, we first add the attribute in a class like below.
public class User {
@Unique(service = UserService.class, fieldName = "email", message = "{email.unique.violation}")
private String email;
// Other fields + getters & setters
}
Note that the validator can of course be used in combination with other Hibernate validators, regardless of whether they are standard or custom. Next, we must implement the fieldValueExists method of our FieldValueExists interface within the UserService class. Note that in this case, UserService is an interface and the implementation is actually named UserServiceImpl, because the dependency is resolved by Spring Framework.
public interface UserService extends FieldValueExists { }
Since the above interface extends the FieldValueExists interface, we do not need to add the fieldValueExists method to our service interface. However, we can of course add more methods if we so desire.
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public boolean fieldValueExists(Object value, String fieldName) throws UnsupportedOperationException {
Assert.notNull(fieldName);
if (!fieldName.equals("email")) {
throw new UnsupportedOperationException("Field name not supported");
}
if (value == null) {
return false;
}
return this.userRepository.existsByEmail(value.toString());
}
}
The logic within the above method can be adapted entirely and freely to fit your needs. In my case, I only wanted to support validating a single field (email), hence why an UnsupportedOperationException exception is thrown if validation is attempted for other fields. The code invokes a method on a repository to check whether or not the value exists within a database. This repository is a Spring Data JPA repository that is injected into the service by Spring Framework, but you could implement this exactly how you wish. The reason why we must perform one or more checks on the field name is explained below.
Solution Shortcomings
Now, I am by no means saying that this solution is flawless, because it is not. Below I will discuss the shortcomings of my solution.
- When the validation method on the service is invoked, we must know which field the validation should be done for, because the annotation might be used on multiple fields within a class. This is why the name of the field is required to be passed as an argument in the annotation. This is not pretty, but I was unsuccessful in finding a way to get the field name from the annotation within the validator class.
- Because this custom validator is intended to be used in various contexts and for various field types, the static type of the value to validate the uniqueness of is Object. This means that we cannot benefit from strong typing within our service implementation, but checking the fieldName parameter, we can cast value to a more suitable type. This is a tradeoff that I was personally willing to accept in order to avoid writing many validators with the only difference being the service class. This would be very easy to do, but that can quickly end up becoming a lot of boilerplate code.
- By default, Hibernate validates entities just before persisting. Unfortunately, this has to be disabled for this approach to work. This is because at the time of persisting, Hibernate is unaware that it is part of a Spring Framework application, and therefore the dependencies are not injected into the custom validator. However, this would also be the case if the static type of the service field was a specific service implementation rather than an interface. There are various suggestions on how to make this work without disabling entity validation at persist time, but I have been unsuccessful in making it work with Spring Framework 4. For information on how to disable this validation, please see below.
- The implementation directly depends on Spring Framework to handle dependency injection, which makes it less portable. This is perfectly fine in my case because I am using Spring Framework, but you might not be. Using it in other contexts should not be too much of a headache, though.
Disabling Hibernate Validation When Persisting Entities
As previously discussed, I have currently not been able to make this validator work without disabling the validation that Hibernate performs by default when persisting entities. As a result, I have had to disable this behavior. There are several ways of accomplishing this, so I will just show you how I did it by configuring an EntityManagerFactory using annotation-based configuration. If you cannot do like this in your particular application, then I kindly ask you to perform a Google search.
@Configuration
public class PersistenceConfig {
@Bean
public EntityManagerFactory entityManagerFactory() throws SQLException {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
String[] packagesToScan = new String[] { ... };
factory.setPackagesToScan(packagesToScan);
factory.setDataSource(this.dataSource());
factory.setValidationMode(ValidationMode.NONE); // <--------
factory.afterPropertiesSet();
return factory.getObject();
}
}
Final Words
Please note that validating uniqueness like this does not guarantee that a value is unique, as it is possible for phantom reads to occur. Basically this means that just because your validator “claims” that a value does not exist for a given field (supposedly by looking it up in a database), it is not guaranteed that the value does not exist when an entity is being persisted. That is why one must also apply a unique constraint to the column within the database to ensure that uniqueness is enforced. This is always good practice, but the chances of facing problems by not doing this are higher for highly concurrent systems.
As discussed above, my solution has a few shortcomings, but I still personally prefer it over other solutions that I have found. That being said, please let me know if you know how to overcome these problems, as I would be very interested to learn how to improve this solution.
I hope this article helped anyone solve this problem. Happy coding and thank you for reading!
9 comments on »Unique Field Validation Using Hibernate and Spring«
Hi thank you for this very good article, it was very helpful
I was working on something similar and didn’t want to disable Hibernate validation, so here is my solution:
1. Remove the ApplicationContext injection.
2. Create a helper class ApplicationContextProvider which implements Spring’s ApplicationContextAware, and make it a Spring bean like this:
3. In the UniqueValidator just replace this code: this.applicationContext.getBean(…)
with:
ApplicationContextProvider.getBean(…)
Hope this helps , thks :)
Hello Mohamed,
I am happy that this article helped you. Thank you very much for sharing your solution – it is much appreciated! I will look at it in more details when I have the time. Thanks again!
Hi, great post Bo.
I have been struggling with the same problem and ended coming up with a very similar solution. Problem is that although it works fine at pojo/entity creation time, does not work at pojo/update time cause existence check must take into account the pojo/entity being validated (i.e. select * from X where validatedField = ? and id = pojo.id). I cannot find a way to obtain a reference to the pojo/entitiy being validated in the ConstraintValidator implementation.
Some ideas? Thanks!
Hello David,
Thank you for your feedback. Off the top of my head, I cannot give you a solution, because I added the fieldName attribute because I could not find a way to refer to the class in which my annotation is used. If you can find a way of doing that, then I would think that you can use reflection to find a class variable with a given annotation (e.g. @Id). Once you find a way to use reflection on your POJO, then I think you should be able to make it work.
I hope you find a solution, and if you do, I would greatly appreciate if you would share your findings.
Good luck, and thank you for your comment!
Yeah!, that was the idea but it seems impossible to access the pojo being validated from the validator. I had to admit defeat and resorted to (in my case) using a Spring MVC validator which in a generic way validates all the unique attributes of the pojo. It feels akward to have two points of validation but it does the job.
IMHO the validation API is kinda flawed. Being unable to access the pojo being validated from the validator prevents a lot of things (I’m thinking about dependent validations like uniqueness scoped by another field).
Thanks for your time :)
I absolutely agree with you! :-)
I have a question about this system in a REST back end environment:
Let’s say that you have a createUser method / endpoint with a User instance as a parameter.
It works just fine.
But what about the editUser method / endpoint with a User instance as a parameter.
We don’t want this validation on the email here. The email already exists obviously.
Any idea on how to switch off this specific validation based on the end point?
Your solution is very good but when using it update case I could not find solution. For instance I want to update user phone field and I already used @Unique constraint that you have created but it always checks from db existing user by phone. In my case I need user’s phone that is updating on current time and new value of phone field. After that I set new value to phone field if new value is the same with previous value I must update it. How I can do that
You can create the @Unique annotation with @Target(ElementType.TYPE) and anotate the class, not the field. This way you can have the pojo in the isValid() method of your custom validatod