Validating Domain Objects in Hibernate Part 2: Enabling Validation

Posted on September 17, 2007 by Scott Leberknight

This is the second in a series of short blogs describing how the Hibernate Validator allows you to define validation rules directly on domain objects where it belongs. In the first article I introduced the Hibernate Validator and showed a simple example of annotating a domain object. In this article I'll show you how to enable event-based validation, meaning when an event like a save or update occurs, Hibernate will automatically validate the objects being saved and throw an exception if any of them are invalid. I'll also show how you can manually validate objects using the validator API.

Prior to Hibernate Core version 3.2.4 (or around thereabouts) enabling event-based validation required explicit configuration. In plain Hibernate (i.e. standalone) you configure event-based validation like this:

<!-- Plain Hibernate Configuration File (e.g. hibernate.cfg.xml) -->
<hibernate-configuration>
  <session-factory>
    <!-- other configuration -->
    <event type="pre-update">
      <listener class="org.hibernate.validator.event.ValidateEventListener"/>
    </event>
    <event type="pre-insert">
      <listener class="org.hibernate.validator.event.ValidateEventListener"/>
    </event>
  </session-factory>
</hibernate-configuration>

Since many people use Hibernate in a Spring environment, you would add the following to the AnnotationSessionFactoryBean in your Spring application context file:

<!-- Spring-based configuration (e.g. applicationContext-hibernate.xml) -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <!-- other configuration -->
  <property name="eventListeners">
    <map>
      <entry key="pre-update">
        <bean class="org.hibernate.validator.event.ValidateEventListener"/>
      </entry>
      <entry key="pre-insert">
        <bean class="org.hibernate.validator.event.ValidateEventListener"/>
      </entry>
    </map>
  </property>
</bean>

So basically all you are doing is configuring the ValidateEventListener event listener class to fire on "pre-update" and "pre-insert" events. So at runtime Hibernate will validate annotated domain objects before inserting or updating anything in the database. There is nothing particularly special here. Hibernate provides a ton of hook points into the various Hibernate events such as save, update, delete, persist, etc. via a listener model, and the Hibernate Validator simply hooks into this mechanism. Note that you need the Hibernate Validator JAR file in your CLASSPATH as it is not part of the core distribution.

As of Hibernate Core 3.2.4 (or somewhere around that version since I can't seem to find it in the release notes anywhere), Hibernate tries to help you out by auto-registering the ValidateEventListener if it is present in the CLASSPATH. Take a look at the following code snippet from the Hibernate AnnotationConfiguration class' buildSessionFactory() method:

// From org.hibernate.cfg.AnnotationConfiguration
public SessionFactory buildSessionFactory() throws HibernateException {
    // ...
    // ...add validator events if the jar is available...
    boolean enableValidatorListeners = ! 
        "false".equalsIgnoreCase( getProperty( "hibernate.validator.autoregister_listeners" ) );
    Class validateEventListenerClass = null;
    try {
        validateEventListenerClass = ReflectHelper.classForName(
                "org.hibernate.validator.event.ValidateEventListener",
                AnnotationConfiguration.class );
    }
    catch (ClassNotFoundException e) {
        //validator is not present
        log.debug( "Validator not present in classpath, ignoring event listener registration" );
    }
    
    // ...now if enableValidatorListeners is true, register a ValidateEventListener...
    // ...for pre-insert and pre-update events automagically (trying to not register if already configured)...
    
    // ...rest of method... 
}

The salient points of the above code are:

  1. Automatic registration occurs unless the hibernate.validator.autoregister_listeners property is "false."
  2. Hibernate uses reflection to determine if the ValidateEventListener class is available in the CLASSPATH.
  3. Hibernate does not register ValidateEventListener if it is already registered, e.g. via explicit configuration.

Regardless of how the configuratio has occurred, once configured everything works exactly the same. So now if you had code that saved a domain object you could catch an InvalidStateException in your code, which is the type of exception Hibernate Validator throws when validation fails. For example, you could write:

List<String> allErrors = new ArrayList<String>();
try {
    session.save(user);
}
catch (InvalidStateException ex) {
    for (InvalidValue error : ex.getInvalidValues()) {
        allErrors.add(humanize(error.getPropertyName()) + " " + error.getMessage());
    }
    // clean up...
}

That's pretty much it. You catch an InvalidStateException and from that exception you can get an array of InvalidValue objects. Each InvalidValue object contains the property name on which the validation error occurred, and a message such as "is required" or "is not a well-formed email address." The messages come from (by default) a standard Java resource bundle named ValidatorMessages.properties so the validation messages are fully internationalized depending on the locale. The property names will be the JavaBean property name, for example "firstName" so you probably need an extra translation step to convert the Java property notation to a human-readable one. In the example above I have a method named humanize that presumably translates "firstName" to "First Name" assuming a US locale.

Last, what if you want to manually validate an object? It is also very simple. The following code shows how to do it:

User user = getUserById(id);  // get object to validate somehow...

ClassValidator<User> validator = new ClassValidator<User>(User.class);
InvalidValue[] errors = validator.getInvalidValues(user);

Pretty simple. This allows you to validate domain objects before sending them to Hibernate to be persisted, which might be beneficial in certain cases. For example, you might want to perform validation in a service layer manually before sending objects to a data access tier which interacts with Hibernate. The one big difference is that event-based validation automatically validates parent and child objects (i.e. the object graph being saved) whereas the manual validation does not unless you annotate the child collection with @Valid.

In this article I've shown how to enable the Hibernate Validator and how to use event-based and manual validation of domain objects. In the next article I'll show you how to create your own validators in case you need more than the ones Hibernate Validator provides out of the box.



Post a Comment:
Comments are closed for this entry.