Wednesday, January 20, 2010

Unique Key Validation

@UniqueConstraint Annotation does not validate the value of entity field. It will not catch the org.hibernate.exception.ConstraintViolationException.


Example:
@Table(name = "user", uniqueConstraints = @UniqueConstraint(columnNames = "username"))

Error:
ERROR [AbstractFlushingEventListener] Could not synchronize database state with session org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update...


SOLUTION: Custom Validator
----------------------------------------
To solve this problem, I created a custom validator to do the validation in JSF before persist the entity.
Seam provides annotations to allow us to use Seam components as JSF converters and validators.


@Name("usernameValidator")
@BypassInterceptors
@org.jboss.seam.annotations.faces.Validator
public class UsernameValidator implements javax.faces.validator.Validator {


public static final String VERIFICATION_MSG_EXPR = "#{messages['validator.duplicateError']}";

public void validate(FacesContext context, UIComponent cmp, Object value)throws ValidatorException
{

String ejbql = "select u from User u where u.username=:username";

String username = (String)value;

EntityManager em = (EntityManager) Component.getInstance("entityManager");
Query query = em.createQuery(ejbql);
query.setParameter("username", username);

List list = query.getResultList();

if (!list.isEmpty()) {

FacesMessage msg = new FacesMessage();
msg.setSummary(Interpolator.instance().interpolate(VERIFICATION_MSG_EXPR));
msg.setSeverity(FacesMessage.SEVERITY_WARN);
throw new ValidatorException(msg);
}
}
}



In JSF:

Registers the Seam component as a JSF validator. Below example is a validator which injects another Seam component. The injected component is used to validate the value.


<h:inputText id="username" value="#{userHome.instance.username}" validator="usernameValidator"/>

4 comments:

  1. this works fine but suppose if i am editing this is not working ie while editing i am forced to change username otherwise its still showing validator.duplicateError , is their any work around for this while editing

    ReplyDelete
  2. hi.... below is the work around i m using. hope can help u..

    in UserHome.java add this method

    public void handleUsernameChange(ValueChangeEvent e) {
    try{
    String newUsername = (String) e.getNewValue();
    String savedUsername = getInstance().getUsername();

    if(!newUsername.equals(savedUsername))
    {
    String ejbql = "select u from User u where lower(u.username)=:Username";

    Query query = getEntityManager().createQuery(ejbql);
    query.setParameter("Username", newUsername.toLowerCase());

    List list = query.getResultList();

    if (!list.isEmpty()) { facesMessages.addToControl("username2", FacesMessage.SEVERITY_ERROR, "#{messages['validator.duplicateError']}");
    }
    }
    }catch(Exception ex){
    System.err.println("exception: "+ex);
    }
    }


    in JSF
    s:fragment rendered="#{!userHome.managed}"
    s:decorate id="usernameField" template="/layout/edit.xhtml"
    ui:define name="label">Username
    h:inputText id="username" size="25" maxlength="25" value="#{userHome.instance.username}" required="true"
    validator="usernameValidator">
    a:support event="onblur" reRender="usernameField" bypassUpdates="true" ajaxSingle="true"/>
    h:inputText>
    s:decorate>
    /s:fragment>
    s:fragment rendered="#{userHome.managed}">
    s:decorate id="usernameField2" template="/layout/edit.xhtml">
    ui:define name="label">Username
    h:inputText id="username2" size="25" maxlength="25" value="#userHome.instance.username}" required="true" valueChangeListener="#{userHome.handleUsernameChange}">
    a:support event="onblur" reRender="usernameField2" bypassUpdates="true" ajaxSingle="true"/>
    /h:inputText>
    /s:decorate>
    /s:fragment>

    ReplyDelete
  3. There's a better solution for "edit" pages:

    You should modify sql as :

    select u from User u where lower(u.username)=:Username and id <> u.id

    ReplyDelete