Omgo's Blog

October 25, 2009

Creating a JSR 303 style unique value annotation and using it in Spring 3 MVC

Filed under: Hibernate, Spring, Validation — aswin @ 4:18 am

I am currently playing with the new JSR 303 validation feature and it looks great.  This post shows how to create a simple annotation that validates whether an input field already exists in the database or not (useful for unique username situations).

So you start by defining a validation annotation for this purpose and would call it “Unique”

package com.omgo.security.domain.validator;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueIDValidator.class)
@Documented
public @interface Unique {
    String message() default "{com.omgo.security.domain.validator.constraints.uniques}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * The mapped hibernate/jpa entity class
     */
    Class<?> entity();

    /**
     * The property of the entity we want to validate for uniqueness. Default name is "id"
     */
    String property() default "id";
}

Now to actually creating the implementation of the validator which we would name as UniqueIDValidator. This validator has dependencies on the Hibernate SessionFactory and this would be injected by Spring as it hooks into the validator framework using the  SpringConstraintValidatorFactory.  Luckily this is all done transparently by the org.springframework.validation.beanvalidation.LocalValidatorFactoryBean, so just declare this in your spring configuration and things would be smooth :).

package com.omgo.security.domain.validator;
import java.io.Serializable;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;

public class UniqueIDValidator implements ConstraintValidator<Unique, Serializable> {

	HibernateTemplate hibernateTemplate;

	private Class<?> entityClass;
	private String uniqueField;

	public void initialize(Unique unique) {
		entityClass = unique.entity();
		uniqueField = unique.property();
	}

	public boolean isValid(Serializable property, ConstraintValidatorContext cvContext) {

		String query = String.format("from %s where %s = ? ", entityClass.getName(), uniqueField);
		List<?> list = hibernateTemplate.find(query, property);

		return list != null && list.size() > 0;
	}

	public SessionFactory getSessionFactory() {
		return hibernateTemplate != null ? hibernateTemplate.getSessionFactory() : null;
	}

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.hibernateTemplate = new HibernateTemplate(sessionFactory);
	}
}

So now that we have a validator that could be used to validate an object, we can use it to annotate our domain objects as follows .

@Entity
@Table(schema = "security", name = "company")
public class Company {
	@Id	@GeneratedValue(strategy=GenerationType.SEQUENCE)
	private Long id;

	@NotNull @Unique(entity=Company.class)
	private String name;
   ..........
   ..........
 }

With our entity object marked with all meta data needed for validation we should try putting this to real use, say by using this in a Spring MVC controller. It is pretty simple to do that with the annotated Spring MVC controller as you can declare the model attribute with a @Valid parameter annotation in your request handler method as the following example shows

	@RequestMapping(method=RequestMethod.POST)
	public String saveCompany(@Valid Company company, BindingResult results) {

	if (results.hasErrors()) {
			return newFormView(modelMap);
		} else {
		    //save the company
			return "redirect:/companies/" + company.getId();
		}
	}

Spring MVC would bind the user submitted form values to the Command/Form object (in this case Company) and apply the validation rules before handing over the bean and the results to your controller for taking further actions. In this example the action was to just forwarded it to the original form page ,in the case of error, where the errors could be displayed.

The spring context has to configured to use the JSR 303 as explained in this post. You could also manually validate this using the Validator as the following spring based JUnit test shows (make sure that you have the spring xml named UniqueValidatorTest-context.xml with the dependencies wired).  In fact there are limitations in the usage of validators in the current version of spring mvc (3.0.0.RC1) and one may need  to  fall back to the not so automatic way of validating  the beans.  For example currently Spring MVC does not allow you to specify cofiguring validation groups directly on your requesthandler methods. You could achieve a lot this by using the JSR 303 redefining default group feature , but if you need control over validations it is really simple, the following test case shows how to run the validators programmatically

package com.idinsight.security.domain.validator;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.idinsight.security.domain.Company;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration()
public class UniqueValidatorTest {

	@Autowired Validator validator;

	@Test
	public void test(){
		TestEntity entity = new TestEntity("inexistant", 10000l);
		Set<ConstraintViolation<TestEntity>> constraintViolations = validator.validate(entity);
		Assert.assertTrue(constraintViolations.size() > 0);
	}
}

class TestEntity {
	@NotNull @Unique(entity=Company.class, property="name")
	String name;

	@NotNull @Unique(entity=Company.class)
	Long id;

       TestEntity(String name, Long id){
            this.name = name;
            this.id = id;
       }
}

The Hibernate validator site has pretty detailed documentation on using the validation framework and includes lots of examples of creating custom validator annotations.  One very interesting features is the ability to group the validations, this would help in segregating validations belonging to multiple layers of the applications. There could be validators that are executed within the service layer (cases where the validators themselves may need to run under a transactional context) and other groups of validations aimed specifically at the presentation tier and such.

5 Comments »

  1. I like this style of uniqueness validation, but have one problem.

    I am using this on a different field (“name”), which has a unique constraint, and which is not the primary key.

    For inserting new records, it works no problem, just as I would expect.

    For updating an existing record, it works with no problem, throwing the validation error when change the “name” to something that already exists. However, when I make no change to that field and only to others, it also throws the validation (because of course this object already exists). This is an issue.

    How would it be possible to modify this code to prevent this? Is there some way to pass in something like a primary key, and append the sql to say primaryKey thisPrimaryKey?

    Thanks!

    Comment by Beau — December 9, 2009 @ 8:14 pm

  2. Yes, that’s a good point, but as you say its hard to generically implement it and would mostly require another field (eg id) . I think it would be quite easy to use this annotation selectively using the groups feature (http://www.jroller.com/eyallupu/entry/jsr_303_beans_validation_using) . But if any one has a better idea please share.

    Comment by omgo — December 10, 2009 @ 2:17 am

  3. Thanks omgo,

    I will look into the groups feature, and let you know what I come up with.

    Beau

    Comment by Beau — December 10, 2009 @ 1:23 pm

  4. Surely line #27 in UniqueIDValidator should be == 0, not > 0?

    Comment by Deejay — June 30, 2011 @ 10:35 am

  5. Omgo, nice post, very useful.

    Ross

    Comment by Ross Huggett — March 2, 2013 @ 6:59 pm


RSS feed for comments on this post. TrackBack URI

Leave a reply to Ross Huggett Cancel reply

Blog at WordPress.com.