Sunday, March 15, 2009

Hibernate Unique validator with Seam



Have you ever wished , if your favorite framework provides some kind of functionality that validates unique constraints against your Database tables.

Not until now , most of us were catching exceptions and look for Sql error code for unique constraint or doing some kind of work around to handle unique constraint issues. Which is not so clean and nice looking.

Fortunately, with JBoss Seam and Hibernate validator we can handle unique constraint problem in a elegant way. And did i say that it will be Generic too. Yes it will work for all of your tables.

Lets see how Jboss Seam will handle Unique constraint problem and returns a nice looking message.

Suppose we have a table called Users with userName and some other property. Do i need to say userName should be unique.


CREATE TABLE Users(
id INT NOT NULL AUTO_INCREMENT
, userName VARCHAR(200) NOT NULL
, password VARCHAR(15) NOT NULL
....
//Some more properties
);

Before looking at User domain class , lets create an annotation called @Unique


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ValidatorClass(UniqueValidator.class)
public @interface Unique {
String message() default " already exists";

String entityName();

String fieldName();
}

@Unique is a simple annotation with 2 properties
entityName - your mapped class
fieldName - property that should be unique for this entity.

And UniqueValidator class



@Name("UniqueValidator")
public class UniqueValidator implements Validator, PropertyConstraint {

//Entity for which validation is to be fired
private String targetEntity;

//Field for which validation is to be fired.
private String field;

public void initialize(Unique parameters) {
targetEntity = ((Unique)parameters).entityName();
field = ((Unique)parameters).fieldName();
}

public boolean isValid(Object value) {
EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
Query query = entityManager.createQuery("select t from " + targetEntity
+ " t where t." + field + " = :value");
query.setParameter("value", value);

try {
query.getSingleResult();
return false;
} catch (final NoResultException e) {
return true;
}
}

public void apply(Property arg0) {

}
}



Now we just have to apply this @Unique annotation to User class




public class User {

@Unique(entityName = "come.XXX.User", fieldName = "userName")
protected String userName;

protected String password;

............

}



Add <s:validateall> in your view

<s:validateall>
<h:outputtext value="User Name">

<ice:inputtext partialsubmit="true" id="ApplicationUser_userName" required="true" value="#{user.userName}">
</ice:inputtext>

<h:message styleclass="error errors" for="ApplicationUser_userName">
</h:message>
</h:outputtext>
</s:validateall>



That's it!! Now Seam will auto magically show you "already exist" message as you will tab out of user name text box. Thats Seam Magic.