DataObject implements a set of methods to validate its state. DataContext calls these methods before performing a commit. If validation fails, the commit is aborted with ValidationException. There are a few clear benefits of validating DataObjects at the application level before committing them to the database:

  • Cayenne can leverage ORM metadata (DataMap) to check for many standard error conditions.
  • Custom validation can be easily integrated into the business objects by overriding standard validation methods.
  • Validation failures can be tracked down to the individual objects and their properties, allowing creation of user-friendly, internationalized validation messages.
  • Relying on database validation for things like mandatory properties, etc., would result in errors that are meaningless or extremely hard to decode in the application context.
  • Application validation partially addresses shortcomings of databases that do not support real transactions (e.g. older MySQL). Validation would trap the whole class of errors that would otherwise result in failure halfway through the commit, leaving database in inconsistent state.
  • Automating of the business objects validation takes away some burden from the UI that now has fewer things to track.

Validation API

Each DataObject participating in commit operation (i.e. those in state NEW, DELETED or MODIFIED , in other words "non-committed") will be validated by DataContext's ObjectStore during commit processing. Depending on the non-committed object state, ObjectStore calls one of the methods described below (description of method behavior is provided for CayenneDataObject implementation):

  • public void validateForInsert(ValidationResult validationResult)
    public void validateForUpdate(ValidationResult validationResult)
    Implementation internally calls validateForSave(..). When overriding, in most cases developers should invoke "super".
  • public void validateForDelete(ValidationResult validationResult)
    This method does nothing by default and exists merely for overriding.
  • protected void validateForSave(ValidationResult validationResult)
    This method only exists in CayenneDataObject (and not in DataObject). It is invoked internally from validateForInsert(..) and validateForUpdate(..), performing some generic validation based on the DataMap information. This includes checking for nulls and for values that exceed their database size limitations. When overriding this method to include custom validation, developers should call "super" in most cases.

Custom validation method implementation would normally append any failures to the provided ValidationResult instance. After validating all non-committed objects, DataContext (or rather its ObjectStore) will check if the ValidationResult is not empty, and throw an exception if there is at least one failure. Typical custom validation method would look like that:

public class Painting extends _Painting {
   protected void validateForSave(ValidationResult validationResult) {
      // check business rules
      if(getEstimatedPrice().doubleValue() <= 0.0) {
                this, // source object of the failure
                Painting.ESTIMATED_PRICE_PROPERTY, // failed property name
                "Painting price must be greater than zero.")); // error message  

Validation Methods with Side Effects

Often validation methods are implemented to modify an object being validated and/or other persistent objects. Cayenne supports such behavior, however a few things should be taken into consideration:

  • Since version 3.0 Cayenne supports lifecycle callbacks that may be a better alternative.
  • If a previously "clean" object becomes "dirty" as a result of the user logic in the "validate" method, such object is not validated.
  • There is no guarantee of a specific order in which dirty objects are validated.

Turning Validation On/Off

Whether DataContext performs validation at all depends on the value of its property validatingObjectsOnCommit. Calling isValidatingObjectsOnCommit() returns currently configured value. Default value (usually "true") is propagated from the parent DataDomain when DataContext is created. This default value can be configured using CayenneModeler as described in Configuring Object Validation section.