Recipe: Using Hibernate event-based validation with custom JSR-303 validators and Spring autowired injection
JSR-303 (Bean Validation) is nice.
It allows to define validation rules directly on beans using simple annotations. Most cases are covered by the base annotations provided by the standard, and it even includes a way to develop custom validators that can be plugged-in as you see fit.
Once your beans are annotated, you can manually trigger validation as simply as:
Set<ConstraintViolation> constraintViolations = validator.validate(annotatedBeanInstance);
The latest and greatest Spring 3 now supports JSR-303 validation out of the box. One only needs this line in Spring configuration XML to enable the integration:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
Even better, custom validators developed in such a Spring environment can even beneficiate from Spring’s @Autowired injection, freebie!
Hibernate itself also offers an integration with JSR-303 through Hibernate events. Enabling the integration with Hibernate allows validation rules to be executed on those events for your model beans:
Will it blend?
At first sight, all of this looks very nice. You get standardized validation directly on your beans and invoking validation is a one-liner.But what happens if you mix all of this together?
In my current project, we wanted to build a custom validator that would check if an e-mail address already existed in the database before saving any instance of our Contact entity using Hibernate. This validator needed a DAO to be injected in order to check for the existence of the e-mail address in the database. To our surprise, what we thought would be a breeze was more of a gale. Spring’s injection did not work at all when our bean was validated in the context of Hibernate’s event-based validation (in our case, the
At first glance, we did all that was required both sides:
- We enabled Spring 3 integration with JSR-303, as per documentation
- We enabled Hibernate event-based integration with JSR-303, as per documentation
After some digging, we found out that Hibernate did not use our Spring
LocalValidatorFactoryBean, but rather used its own ValidatorFactory. It became quite obvious that it all did not blend that much.
Fortunately, a solution exists, and at this moment does not seem documented. Hence this page!
I’ve created a quick Maven project that I’ve published on GitHub that showcases a full working solution. Refer to the README on GitHub for details.
To summarize the solution, two things are required, in addition to the standard integration steps for both frameworks (Hibernate and Spring).
Spring XML configuration
<!-- The LocalValidatorFactoryBean is able to instantiate custom validators and inject autowired dependencies. --> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/> <!-- This is the key to link Spring's injection to Hibernate event-based validation. Notice the first constructor argument, this is our Spring ValidatorFactory instance. --> <bean id="beanValidationEventListener" class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"> <constructor-arg ref="validator"/> <constructor-arg ref="hibernateProperties"/> </bean> <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="annotatedClasses"> <list> <value>com.github.davidmarquis.model.User</value> </list> </property> <property name="hibernateProperties" ref="hibernateProperties"/> <!-- A reference to our custom BeanValidationEventListener instance is pushed for all events. This can of course be customized if you only need a specific event to trigger validations. --> <property name="eventListeners"> <map> <entry key="pre-update" value-ref="beanValidationEventListener"/> <entry key="pre-insert" value-ref="beanValidationEventListener"/> <entry key="pre-delete" value-ref="beanValidationEventListener"/> </map> </property> </bean>
The key part here is to force Hibernate to use a custom BeanValidationEventListener that will use Spring’s LocalValidatorFactoryBean instead of creating its own. This is the missing piece of the puzzle.
In some cases, we found out that Hibernate still adds its own BeanValidationEventListener in addition to the one we define. Obviously, the second event listener (the one created by Hibernate) is not configured correctly so all validations based on injected beans fail with nice little NullPointerException. Specifying this property disables Hibernate automatic configuration process, and the duplicate event listeners all goes away.
That’s it! Hope this will be of some help!