views:

482

answers:

4

What is the best mechanism for preventing constraint violation checks before creation | modification of an entity?

Suppose if the 'User' entity has 'loginid' as the unique constraint, would it be wise to check if there is an user entry already with this loginid name before creation or modification.

OR

Would you let the database throw an ConstraintViolationException and handle this message appropriately in the UI layer. Where should such checks be enforced in the jboss seam framework.

Note: Currently no such checks are enforced on the seam-gen code.

We currently use Seam 2.2, Richfaces with Hibernate.

+1  A: 

My advice is that if you can check a condition then check it i.e. in your case a UserExists method call. Throwing exceptions is expensive and is intended for exceptional cases normally related to things outwith your control e.g. disc access etc

You would generally perform this check in your business logic before calling for the entity to be added to the database.

David
Yes, my only concern with this approach is that few queries have to be fired for almost any creation or modification of the record. I am just looking for the best approach which users prefer. Thanks
Joshua
+3  A: 

Even if you check the condition in your code before persisting the user object there is always a chance that someone will created a duplicate loginid between the time you check and when you persist the new User.

However it'll be easier to display an appropriate error message in the UI if you do an explicit check. If you have multiple contraints on the table catching the ConstraintViolationException won't allow you to easily determine which constraint has been violated.

So I would do both. Assuming you're extending from Seam's EntityHome:

  1. In the persist() method run a query to ensure that the loginid is unique. If it isn't add an error message to the appropriate control and return null.
  2. Wrap the call to super.persist() and catch the ConstraintViolationException, displaying a generic duplicate error message

EDIT

As Shervin mentioned creating a JSF Validator is a great idea (replacing) #1 above, but you should still expect the worst and catch ConstraintViolationException.

mtpettyp
How do you prevent these issues if a standalone client makes a direct JPA call. How can we make the code re-usable across these scenarios?
Joshua
Does the standalone client always make a direct JPA call, or can you force it to go through a DAO of some sort?
mtpettyp
We are using Seam components (EntityHome, EntityQuery interfaces) which currently serve as the glue between user interface and the JPA persistence layer.It's not clear to me, if I could enforce the standalone clients to use the Seam abstraction layer for persistence instead of the JPA layer directly. If Seam layer has to be present, then Seam libraries needs to be shipped as part of the client toolkit.Not sure what is the best approach here
Joshua
That can cause pretty much extra queries, which will impact performance badly.
pihentagy
+3  A: 

I disagree with handling ConstraintException. I have written a validator that checks duplicates before saving, and it works great.

Here is an example checking duplicate emails.

@Name("emailValidator")
@Validator
@BypassInterceptors
@Transactional
public class UniqueEmailValidator implements javax.faces.validator.Validator, Serializable {

private static final long serialVersionUID = 6086372792387091314L;

@SuppressWarnings("unchecked")
public void validate(FacesContext facesContext, UIComponent component, Object value) throws ValidatorException {
    EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
    String newEmail = (String) value;
    String oldEmail = String.valueOf(component.getAttributes().get("oldEmail"));
    if (oldEmail != null && !oldEmail.equalsIgnoreCase(newEmail)) {
        List<User> users = entityManager.createQuery(
                "SELECT DISTINCT u FROM " + User.class.getName() + " p where lower(p.fromEmail) = :email").setParameter("email",
                newEmail.toLowerCase()).getResultList();
        if (!users.isEmpty()) {
            Map<String, String> messages = Messages.instance();
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, messages.get("admin.emailexists"), messages
                    .get("admin.emailexists")));
        }
    }
}

}

And in your form (xhtml) you write:

<s:decorate template="/layout/definition.xhtml">
        <ui:define name="label">#{messages['processdata.email']}</ui:define>
        <h:inputText id="fromEmail" size="30" required="true" value="#  {userAdmin.existingUser.fromEmail}">
            <f:validator validatorId="emailValidator"/>
            <f:attribute name="oldEmail" value="#{userAdmin.existingUser.fromEmail}" />
            <s:validate />
        </h:inputText>
    </s:decorate>

This way it will always validate the field before saving. You can even put an a:support tag to validate when focus is changed.

Shervin
If your database handles unique, then it will throw exception, and your general exception handler will fix it.
Shervin
There is still a chance that your DB could be updated with a duplicate email between the "Process Validations" and "Update Model" JSF phases and a ConstraintException would be thrown.
mtpettyp
A: 

Excellent! I spent a whole day finding a way to successfully create a UniqueValidator! Thanks a lot!

Jose Vieira