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:

  1. pre-insert
  2. pre-update
  3. pre-delete

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 pre-insert event).

At first glance, we did all that was required both sides:

  1. We enabled Spring 3 integration with JSR-303, as per documentation
  2. 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.

hibernate.properties

javax.persistence.validation.mode=none

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!

Advertisements

IntelliJ IDEA 10 will be awesome!

Jetbrains just published a page detailing the new features that will be included in the upcoming release of their flagship product IntelliJ IDEA 10.

Among the new stuff, I noted those few very interesting improvements:

  • Android development tools now included in the free community version (nice move Jetbrains!)
  • Maven dependencies in Android projects
  • Custom integration of GitHub as VCS
  • Grails: Autocompletion of controller and action names in GSPs
  • Zen coding support for HTML and CSS
  • Improved Python and Ruby support
  • Support for Django projects (oh yes!)

Jetbrains are really positioning themselves to become THE IDE of choice for many software developers. With their excellent support for many languages and frameworks, IntelliJ is becoming the one-stop shop for software developers. As software developers, learning a new platform or language often requires a lot of involvement (new concepts, new tools, new IDEs, etc.) Having such a wide support in one IDE eases the learning curve of new tools or technologies, and encourages us to explore!

Thanks Jetbrains! You literally make our job more enjoyable!