Unique Constraint with Hibernate Validator and Spring
Sometime ago I was fighting with Hibernate Validator, trying to implement an unique constraint and I could not figure out how to create one that requires database access:
Then, while studying the new validation support offered by Spring, I just discovery that using Spring I can inject any bean inside my ConstraintValidators. From Spring documentation:
And here is how you can use that Spring feature to implement unique constraint.THE UNIQUE ANNOTATIONIt is very simple and direct. You can see details about implementing your custom annotations in Hibernate Validator docs. @Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=UniqueConstraintValidator.class)
@Documented
public @interface Unique {
String message() default "{constraints.unique}";
Class<?> entity();
String field();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}You can also see the code at GitHub, where trapo is hosted. If I decide to change something, you can get an updated version there. Once you have the @Unique annotation, you must implement the ConstraintValidator referenced by "validationBy" property in @Constraint annotation above. Here is the code using a bean injected by Spring: public class UniqueConstraintValidator implements ConstraintValidator<Unique, String> { @Autowired private SessionFactory sessionFactory;
private Class<?> entity;
private String field;
public void initialize(Unique annotation) {
this.entity = annotation.entity();
this.field = annotation.field();
} public boolean isValid(String value, ConstraintValidatorContext context) {
if(StringUtils.isEmpty(value)) {
return false;
}
return query(value).intValue() == 0;
} private Number query(String value) {
HibernateTemplate template = new HibernateTemplate(sessionFactory);
DetachedCriteria criteria = forClass(entity)
.add(eq(field, value))
.setProjection(count(field));
return (Number)template.findByCriteria(criteria).iterator().next();
} }Just like the @Unique annotation, you can find the code in GitHub. Now you just need a Validator bean that can be injected in the classes that need it. <bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> Voilá!THE UGLY PARTRight now, Hibernate Validor don't offer any way to know from where your annotations are coming. If you take some time to think about the ConstraintValidator above, you will notice that I manually configure the entity class and field information that were annotated. I need that information to know what query I must execute in order to validate the uniqueness of the field. Bad to me. Really bad. Now I have to provide this information by myself annotating the field like the code highlighted below: @NotEmpty @Unique(entity = Forum.class, field = "name")
private String name;I can even hear the DRY concept crying! I can't also find a way to reflect this to my schema. Maybe Hibernate Validator can offer some interfaces that we can use to implement all this stuff.
The problem is that your validators can't access sessionFactory or any other interface to the database. I know that I can break some rules and instantiate a sessionFactory/session/connection directly inside the Validator, but I dislike to do wrong things consciously. So, I looking for alternatives.
Then, while studying the new validation support offered by Spring, I just discovery that using Spring I can inject any bean inside my ConstraintValidators. From Spring documentation:
By default, theLocalValidatorFactoryBeanconfigures aSpringConstraintValidatorFactorythat uses Spring to create ConstraintValidator instances. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.
And here is how you can use that Spring feature to implement unique constraint.THE UNIQUE ANNOTATIONIt is very simple and direct. You can see details about implementing your custom annotations in Hibernate Validator docs. @Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=UniqueConstraintValidator.class)
@Documented
public @interface Unique {
String message() default "{constraints.unique}";
Class<?> entity();
String field();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}You can also see the code at GitHub, where trapo is hosted. If I decide to change something, you can get an updated version there. Once you have the @Unique annotation, you must implement the ConstraintValidator referenced by "validationBy" property in @Constraint annotation above. Here is the code using a bean injected by Spring: public class UniqueConstraintValidator implements ConstraintValidator<Unique, String> { @Autowired private SessionFactory sessionFactory;
private Class<?> entity;
private String field;
public void initialize(Unique annotation) {
this.entity = annotation.entity();
this.field = annotation.field();
} public boolean isValid(String value, ConstraintValidatorContext context) {
if(StringUtils.isEmpty(value)) {
return false;
}
return query(value).intValue() == 0;
} private Number query(String value) {
HibernateTemplate template = new HibernateTemplate(sessionFactory);
DetachedCriteria criteria = forClass(entity)
.add(eq(field, value))
.setProjection(count(field));
return (Number)template.findByCriteria(criteria).iterator().next();
} }Just like the @Unique annotation, you can find the code in GitHub. Now you just need a Validator bean that can be injected in the classes that need it. <bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> Voilá!THE UGLY PARTRight now, Hibernate Validor don't offer any way to know from where your annotations are coming. If you take some time to think about the ConstraintValidator above, you will notice that I manually configure the entity class and field information that were annotated. I need that information to know what query I must execute in order to validate the uniqueness of the field. Bad to me. Really bad. Now I have to provide this information by myself annotating the field like the code highlighted below: @NotEmpty @Unique(entity = Forum.class, field = "name")
private String name;I can even hear the DRY concept crying! I can't also find a way to reflect this to my schema. Maybe Hibernate Validator can offer some interfaces that we can use to implement all this stuff.