Tuesday, July 24, 2007

Data Validation in Hibernate

While it's important to build data validation into as many layers of a Web application as possible, it's traditionally been very time-consuming to do so, leading many developers to just skip it - which can lead to a host of problems down the road. But with the introduction of annotations in the latest version of the Java™ platform, validation got a lot easier. In this article, author shows you how to use the Validator component of Hibernate Annotations to build and maintain validation logic easily in your Web apps.

Java SE 5 brought many needed enhancements to the Java language, none with more potential than annotations. With annotations, you finally have a standard, first-class metadata framework for your Java classes. Hibernate users have been manually writing *.hbm.xml files for years (or using XDoclet to automate this task). If you manually create XML files, you must update two files (the class definition and the XML mapping document) for each persistent property needed. Using HibernateDoclet simplifies this (see Listing 1 for an example) but requires you to verify that your version of HibernateDoclet supports the version of Hibernate you wish to use. The doclet information is also unavailable at run time, as it is coded into Javadoc-style comments. Hibernate Annotations, illustrated in Listing 2, improve on these alternatives by providing a standard, concise manner of mapping classes with the added benefit of run-time availability.
Listing 1. Hibernate mapping code using HibernateDoclet

/** 
* @hibernate.property column="NAME" length="60" not-null="true"
*/
public String getName() {
return this.name;
}

/**
* @hibernate.many-to-one column="AGENT_ID" not-null="true" cascade="none"
* outer-join="false" lazy="true"
*/
public Agent getAgent() {
return agent;
}

/**
* @hibernate.set lazy="true" inverse="true" cascade="all" table="DEPARTMENT"
* @hibernate.collection-one-to-many class="com.triview.model.Department"
* @hibernate.collection-key column="DEPARTMENT_ID" not-null="true"
*/
public List getDepartment() {
return department;
}

If you use HibernateDoclet, you won't be able to catch mistakes until you generate the XML files or until run time. With annotations, you can detect many errors at compile time, or, if you're using a good IDE, during editing. When creating an application from scratch, you can take advantage of the hbm2ddl utility to generate the DDL for your database from the hbm.xml files. Important information -- that the name property must have a maximum length of 60 characters, say, or that the DDL should add a not null constraint -- is added to the DDL from the HibernateDoclet entries. When you use annotations, you can generate the DDL automatically in a similar manner.

While both code mapping options are serviceable, annotations offer some clear advantages. With annotations, you can use constants to specify lengths or other values. You have a much faster build cycle without the need to generate XML files. The biggest advantage is that you can access useful information such as a not null annotation or length at run time. In addition to the annotations illustrated in Listing 2, you can specify validation constraints. Some of the included constraints available are:

  • @Max(value = 100)
  • @Min(value = 0)
  • @Past
  • @Future
  • @Email

When appropriate, these annotations will cause check constraints to be generated with the DDL. (Obviously, @Future is not an appropriate case.) You can also create custom constraint annotations as needed.

Validation and application layers

Writing validation code can be a tedious, time-consuming process. Often, lead developers will forgo addressing validation in a given layer to save time, but it is debatable whether or not the drawbacks of cutting corners in this area are offset by the time savings. If the time investment needed to create and maintain validation across all application layers can be greatly reduced, the debate swings toward having validation in more layers. Suppose you have an application that lets a user create an account with a username, password, and credit card number. The application components into which you would ideally like to incorporate validation are as follows:

  • View: Validation through JavaScript is desirable to avoid server round trips, providing a better user experience. Users can disable JavaScript, so while this level of validation is good to have, it's not reliable. Simple validations of required fields are a must.
  • Controller: Validation must be processed in the server-side logic. Code at this layer can handle validations in a manner appropriate for the specific use case. For example, when adding a new user, the controller may check to see if the specified username already exists before proceeding.
  • Service: Relatively complex business logic validation is often best placed into the service layer. For example, once you have a credit card object that appears to be valid, you should verify the card information with your credit card processing service.
  • DAO: By the time data reaches this layer, it really should be valid. Even so, it would be beneficial to perform a quick check to make sure that required fields are not null and that values fall into specified ranges or adhere to specified formats -- for instance, an e-mail address field should contain a valid e-mail address. It's better to catch a problem here than to cause avoidable SQLExceptions.
  • DBMS: This is a common place to find validation being ignored. Even if the application you're building is the only client of the database today, other clients may be added in the future. If the application has bugs (and most do), invalid data may reach the database. In this event, if you are lucky, you will find the invalid data and need to figure out if and how it can be cleaned up.
  • Model: An ideal place for validations that do not require access to outside services or knowledge of the persistent data. For example, your business logic may dictate that users provide at least one form of contact information, either a phone number or an e-mail address; you can use model layer validation to make sure that they do.

A typical approach to validation is to use Commons Validator for simple validations and write additional validations in the controller. Commons Validator has the benefit of generating JavaScript to process validations in the view. But Commons Validator does have its drawbacks: it can only handle simple validations and it stores the validation definition in an XML file. Commons Validator was designed to be used with Struts and does not provide an easy way to reuse the validation declarations across application layers.

When planning your validation strategy, choosing to simply handle errors as they occur is not sufficient. A good design also aims to prevent errors by generating a friendly user interface. Taking a proactive approach to validation can greatly enhance a user's perception of an application. Unfortunately, Commons Validator fails to offer support for this. Suppose you want your HTML to set the maxlength attribute of text fields to match the validation or place a percent sign (%) after text fields meant to collect percentage values. Typically, that information is hard-coded into HTML documents. If you decide to change your name property to support 75 characters instead of 60, in how many places will you need to make changes? In many applications, you will need to:

  • Update the DDL to increase the database column length (via HibernateDoclet, hbm.xml, or Hibernate Annotations).
  • Update the Commons Validator XML file to increase the max to 75.
  • Update all HTML forms related to this field to change the maxlength attribute.

A better approach is to use Hibernate Validator. Validation definitions are added to the model layer through annotations, with support for processing the validations included. If you choose to leverage all of Hibernate, the validator helps provide validation in the DAO and DBMS layers as well. In the code samples that follow, you will take this a step further by using reflection and JSP 2.0 tag files, leveraging the annotations to dynamically generate code for the view layer. This will remove the practice of hard-coding business logic in the view.

Hibernate DAO implementations use the validation annotations as well, if you want them to. All you need to do is specify Hibernate event-based validation in the hibernate.cfg.xml file.

2 Comments:

Unknown said...

Hi, great post!

As a supplement to this information I would love to see some code samples on Hibernate Validation.

Like have you tried this out with writing your own complex validators?

Other experience?

// andreas

IT Efforts said...

Hi, Andreas!
Try to describe the code samples for future posts