Bean validation in Java EE
This article talks about the process of validation in Java EE, more specifically about Hibernate Validation and Bean Validation. We start by describing why we need validation, what solutions are available, how we use them and why they are great. We then proceed to describe their limitations, and offer proposals for resolving those limitations in the hope that the future Bean Validation standard will incorporate our (or similar) solutions.
What is validation and what is wrong with Java EE validation.
If you are writing your database persistence interface in Java, chances are you will be using the Java persistence API. It abstracts database tables as Java classes (entities in the local jargon), database table columns as object properties (in the Java Bean sense), and database table rows as instances of those classes. The mapping between Java classes and the database model is done using annotations.
This
API
(javax.persistence
) defines a few ways to put constraints on your
properties, for instance
column
or
multi-column
unicity, or
nullability
of columns.
If you try to save (persist or merge in the local jargon) entities in which those constraints have been violated, you will get an ugly exception, and the transaction will probably be rolled back (if that is what you asked for).
When I say ugly exception I mean an exception which has no clear semantics as to what the exception class is, how to get to it (they nest exceptions in non-standard way), how to get to the name of the violated constraint(s) (which incidentally cannot be named by hand in the annotation), and does not allow an application to specify a message to display to signal errors of a particular constraint.
Additionally, it is not possible with Java persistence to specify custom validation rules for entities (the whole entity) or properties.
What is Hibernate validation and what is it good for?
Hibernate provides ways to do custom validation for entities and properties. Work is under way to standardise Hibernate Validation in JSR-303 which is discussed openly and actively in this forum. We will discuss this JSR further in this article.
We should start with a simple example of how to define and edit an entity with JPA, Hibernate and our framework of choice: Seam.
Our validated entity
Here we start simple by having only one interesting property:
emailAddress
which should not be null, and should be a valid email
address. Hibernate Validation declares validation constraints using
Java
annotations, which can be put on entity bean property fields or
getter methods, much in the same way that you define your database
mappings in JPA.
Hibernate Validation comes with many predefined validation constraint
annotations, and provides a way to define your own if you need anything
else. To solve our problem we are going to use the @NotNull
and
@EMail
annotation constraints on our emailAddress
property which
will declare that this property must not be null and must be a valid
email address:
Person.java
@Entity
public class Person {
@GeneratedValue
@Id
private long id;
@NotNull
@EMailValidator
private String emailAddress;
/* Follows getter/setter methods */
}
If we try to persist a Person
with a null
or invalid email address,
the JPA layer will throw an InvalidStateException
(because Hibernate
Validation enforces validation constraints automatically when saving an
entity). This exception will possibly be wrapped by a transaction
exception. But these validation constraints are more useful for other
reasons:
-
They translate to DB-level constraints (well,
@NotNull
does) -
They validate DB-level generated entities as well (if you import them by hand in SQL say)
-
They are automatically hooked in JSF views by Seam, for JSF validation
Our view
Here in the view where we edit a Person we instruct Seam to validate
each field (the <s:validateAll>
tag), and place the validation
messages after each invalid field (the afterInvalidField
facet). This
will use the validation constraints we defined in the Entity to validate
the values during the
Process
Validations JSF phase.
edit.xhtml
...
<h:form>
<s:validateAll>
<f:facet name="afterInvalidField">
<s:message/>
</f:facet>
<p>
<label for="emailAddress">E-mail address</label>
<br/>
<s:decorate>
<h:inputText id="emailAddress" value="#{editedPerson.mailAddress}"/>
</s:decorate>
</p>
<p>
<h:commandButton action="#{personAction.save}" value="Save"/>
<h:outputText value=" or "/>
<h:commandLink action="#{personAction.cancel}">Cancel</h:commandLink>
</p>
</s:validateAll>
</h:form>
...
As you can see it is quite straightforward.
##Our action bean
This is the code which will be invoked on the server when the view
described above will be invoked. It uses the personID
request
parameter (injected by Seam using the
@RequestParameter
annotation) to load a Person
object from the database and outject it
in the editedPerson
variable using a factory method
(initEditedPerson
) which will be invoked when editedPerson
is
null
.
Once the editedPerson
has been sent to the view for editing, there are
two ways out of the view: canceling (calling the cancel()
method) or
saving (calling save()
). Canceling simply does nothing, and saving is
as straightforward as instructing the persistence layer (PersonDAO
) to
save our entity in the database, since it has already been automatically
validated.
PersonActionBean.java
@Stateful
@Name("personAction")
public PersonActionBean implements PersonAction {
@RequestParameter
private Long personID;
@In(required = false)
@Out
private Person editedPerson;
@EJB
private PersonDAO personDAO;
@Begin
@Factory("editedPerson")
private void initEditedPerson(){
editedPerson = personDAO.findPersonById(personID);
}
@End
public void cancel(){}
@End
public void save(){
personDAO.save(editedPerson);
}
}
More elaborate validation using custom validators
Now that the basics about Hibernate Validation have been explained, we still have to explain two important features: custom validation constraints, and custom messages.
We have noticed that users using our application were able to save local
email addresses (email addresses which do not contain an @
or have a
host name with no domain after it). These local email addresses are
widely used in local or private networks, and are perfectly
valid email addresses, but they
cannot be used outside of those networks, so they cannot be reached
globally, which means we cannot contact those people.
The @EMail
constraint validation will accept both local and global
email addresses, because they are both valid, which is why these users
have been able to submit those local email addresses. So we have to
define our own validation constraint which will refuse local email
addresses.
This is very easy to do in Hibernate Validation: we have to define a new
annotation (@NonLocalEmail
) which will be used on our property, and
point to a class responsible for the validation
(NonLocalEmailValidator
):
NonLocalEmail.java
@Documented
@ValidatorClass(NonLocalEmailValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NonLocalEmail {
String message() default "{validator.email}";
}
There are several points of interest in the previous annotation:
-
@ValidatorClass
is the annotation which points to the validation class responsible for the validation logic (this annotation is just a marker: annotations do not contain code). -
@Target
specifies that this validation constraint can be applied on fields and methods. -
the
message
property is a standard Hibernate Validation validator annotation property which will be used by the JSF views in order to provide a meaningful error message when the validation fails. It can be overridden by the annotation user, and holds a default value for the message. The use of curly braces in a message means that the message should be loaded from a localised resource bundle rather than embedding localised messages in the code.
As for the actual class containing the validation logic, we will simply
extend the EmailValidator
class to add a check on the domain-part of
the email address:
NonLocalEmailValidator.java
public class NonLocalEmailValidator extends EmailValidator
implements Validator<NonLocalEmail> {
public void initialize(NonLocalEmail annotation){}
public void isValid(Object email){
// null values are validated by other validators
if(email == null)
return true;
boolean validEmail = super.isValid(email);
// if the address is not even a valid email,
// it cannot possibly be a valid non-local email
if(!validEmail)
return false;
// now check that it has a domain part
String emailValue = (String)email;
// does it have an '@' sign?
int atIndex = emailValue.indexOf('@');
if(atIndex == -1)
return false;
// does it have a fully qualified domain name after it?
return emailValue.indexOf('.', atIndex+1) != -1;
}
}
As you can see, all we have to do is implement the Validator
interface, and define two methods. The initialize
method is used if
our validator logic can be parameterised by the constraint annotation,
which is not the case here. The isValid
method takes a value and
checks whether this value is a valid non-local email address. All very
straightforward and incredibly nice.
What are the limitations?
Now that we hope to have convinced you that Hibernate Validation is the way to go because it is so nice and allows you to not duplicate your validation code, we have to admit it has a number of limitations that we’ve hit (not theoretical limitations, but limitations that forced us into duplicating our code and bypassing the automatic validation integration we’ve described earlier with Seam).
Integration with JPA constraints
JPA actually comes with several constraints declarations such as:
-
@Column(nullable = false)
which is the JPA equivalent to Hibernate Validation’s@NotNull
. -
@Column(unique = true)
which checks for column unicity in the database and has no Hibernate Validation equivalent. -
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"firstColumn", "secondColumn"})})
which checks for multi-column unicity in the database and has no Hibernate Validation equivalent.
The duplication of the "NOT NULL" database constraint between JPA and Hibernate Validation is not merely unfortunate:
-
@Column(nullable = false)
does not allow us to specify a custom error message. -
@Column(nullable = false)
generates a different exception than Hibernate Validation when attempting to persist an invalid entity. -
@Column(nullable = false)
is not used by Hibernate Validation or Seam when checking for invalid values in the view. -
@NotNull
only generates database-level constraints when using Hibernate for persistence (which is a moot point currently since Hibernate Validation is mostly used with Hibernate persistence, but will become relevant once standardised as JSR-303)
Furthermore, for the same reasons, unicity constraints defined in JPA cannot be localised, generate a different exception, and are not used in Hibernate Validation and Seam. What is worse though is that they cannot be replaced by an equivalent custom Hibernate Validation constraint for several practical reasons (API problems we can overcome), and one more fundamental and implacable reason: unicity can only be checked reliably while committing the transaction. Indeed, because it depends on other values in the database, nothing prevents other concurrent transactions from modifying other values after you’ve checked manually for unicity and before you commit your transaction (aside from locking).
Forgetting the fundamental issue, we’ve attempted to implement our own unicity constraint in Hibernate Validation, for sport mainly, and in order to check if that framework was really capable of providing an alternative to JPA’s unicity constraints.
Our attempt at checking for unicity in Hibernate Validation
Let us start simply by adding a single-column unicity constraint to Hibernate Validation on our entity:
Person.java
@Entity
public class Person {
...
@NotNull // instead of @Column(nullable = false)
@Unique // instead of @Column(unique = true)
@EMailValidator // no JPA equivalent
private String emailAddress;
...
}
Here is how the @Unique
annotation would be defined:
Unique.java
@Documented
@ValidatorClass(UnicityValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Unique {
String message() default "{validator.unique}";
}
And here is how we would define the validation class:
UnicityValidator.java
public class UnicityValidator implements Validator<Unique> {
public void initialize(Unique annotation){}
public void isValid(Object value){
// null values are validated by other validators
if(value == null)
return true;
// here we check for unicity by checking if any other entity
// of the same class holds the same value
// ... wait a sec ... how do we do that without having
// the entity and the property we are validating???
Query query = getEntityManager().createQuery("SELECT count(x) FROM ?? x"
+ " WHERE x.?? = :value AND x.id != :id LIMIT 1");
query.setParameter("value", value);
query.setParameter("id", ??);
return ((Number)query.getSingleResult()).intValue() == 0;
}
}
As you can see, we only get the value we are validating (a particular email address), and with only that we simply cannot check for unicity: we need to know the type of object we are validating, and the particular property we are checking for unicity.
There are five ways out of this limitation:
-
Change
@Unique
into@UniquePersonEmail
and make the validation code specific to our particular entity type and property. This is rather inelegant as it does away with all the genericity we expect of such a validation constraint. -
Add some parameters to
@Unique
to specify the entity class and property name so they can be used in the validator class. Using this annotation would require silly syntax such as@Unique(type = Person.class, property = "emailAddress")
, and really this is inelegant too [Hibernatevalidation-footback1]# 1. -
Give up, but we never give up.
-
Extend the Hibernate Validation API to provide us with the required information. It is fairly easy to extend Hibernate Validation so that the
Validator.isValid
method takes an extra two parameters (the bean instance and the property name), which would be enough for our single-column unicity check. -
Use a bean-level validator. This is a validator which will validate the entire bean, and as such it will be passed the entire bean instance in its
isValid
method, so it can access the bean type and properties. In order to specify the names of the properties which have to be unique we can simply reuse the @UniqueConstraint annotation to specify the sets of unique columns. This has two downsides: it puts the validation constraint away from the property we are validating (not that far away, as it stays in the same file), but more importantly, Seam does not use bean-level validation to validate entities, so our view will not be validated.
Since we personally dislike the first 3 options, we will discuss the last two:
-
Extending Hibernate Validation to fix single-column unicity constraints is not enough, because it will not provide us with multi-column constraints. On the other hand it is necessary if we are to provide friendly single-column unicity constraints in line with JPA (even with the duplication).
-
Because Seam does not invoke bean-level validation, using bean-level validation is not the solution either.
So we are left with the option of doing both. Extending Hibernate Validation to provide more information about the bean and the annotated property for property-level constraints and extending Seam so that bean-level validation is executed during the Process Validations lifecycle. Any bean-level validation error messages would be displayed globally rather than next to the edited field since they cannot be associated to a particular property.
Our modifications in Hibernate Validation is very straight-forward and
backwards-compatible: we define a subclass of Validator
called
ExtendedValidator
which provides us with the appropriate information
when validating:
public interface ExtendedValidator<A extends Annotation> extends Validator<A> {
/**
* Returns true if the given bean's property can be set to the given value
*/
public boolean isValid(Object bean, String propertyName, Object value);
}
This method is then invoked in ClassValidator
when the validator
happens to implement ExtendedValidator
. This makes sure that all
previously-defined validators still work. We then have to overload the
Validator.getPotentialInvalidValues
method with an extra parameter for
the bean instance, which we use in Seam.
In Seam we then still have to invoke bean validation, but this is at
odds with the approach during the Process Validations lifecycle, which
does not set the bean properties but uses
Validator.getPotentialInvalidValues
to check for validity without
touching the bean instance. Because there is no equivalent potent
equivalent to bean-level validation, something deeper has to change.
What then? We’re not sure, but we’re still working on the solution.
Additionally, it would be really nice if bean-level validation could
specify different error messages for different errors, as well as
specify more than one error (not just returning false
) and map errors
to property names, so that a bean-level validator could do just as much
as property-level validators.
Conditional and event-based validation
When we integrated web services in our application, we decided to go with bean validation all the way, and get rid of all the validation (validation: not permissions) in the web services frontend. We simply attempt to use whatever the client gives us and the validating persistence layer will complain if needed, at which point we can easily access the error message and format it for the web service client. We just had to move the validation from the action beans into the persistence tier.
While this seems like a good idea, our validation is actually more complex than what we abstracted into the entity beans. In other words, we got hit by limitations in Hibernate Validation. This includes what has been described earlier: the lack of bean-level validation in Seam, its limitations in error reporting and property association, and the lack of support for unicity constraints in Hibernate Validation.
We also discovered that we had more fundamental problems with validation: our beans actually have states, in the sense than some properties should be validated differently based on other properties, thus creating a dependency graph. To give you an example, suppose we add SMS integration in our system, and users can now be contacted either by email, SMS, or both, but having neither email nor SMS is invalid. How can we validate this?
Person.java
@Entity
public class Person {
...
@NotNull // iff phoneNumber is null
@Unique
@EMailValidator
private String emailAddress;
@NotNull // iff emailAddress is null
@Unique
@PhoneNumberValidator
private String phoneNumber;
...
}
Clearly with property-level validation there is no way to specify that one of those can be null if and only if the other is not null. We are left with bean-level validation, which has several drawbacks:
-
Not used by Seam (our view layer)
-
Does not give meaningful error messages
-
Does not map to properties
-
Moves the validation away from the bean where it will end up out-of-sync with the bean after refactoring
-
Introduces code duplication: how many beans do we have where one property out of several is required?
Not happy with bean-level validation, we set out to declare that we need conditional validation: validation which is enabled or disabled based on some conditions. Just at the same time, we noticed that there is a JSR under way to standardise Bean Validation: JSR-303, so we set out to look at it and see if it solves our problems.
Bean Validation (JSR-303)
JSR-303 is a new JSR which is aimed at providing a standard way to validate a Java Bean. It is edited by Emmanuel Bernard (author of Hibernate Validation), and consists mainly in abstracting Hibernate Validation from its Hibernate dependency so that it can be used to validate any Java Bean, persistent or not. In a spirit of transparency and openness, he has opened a forum where everyone can give feedback on the JSR as it evolves. We should point out that we think Emmanuel Bernard is doing great work, not only because we’re using software he wrote, but also because he’s doing us all a favour by standardising Bean Validation, which is something that will prove very useful once people understand its full potential.
The main differences we see with Hibernate Validation are validation groups and partial validation, which provides a way to declare several "layers" or "sets and subsets" of validation, that can be enabled or disabled when validating programmatically. The JSR also defines an entire reflection framework to access the validation constraints at run-time.
While it may seem that validation groups are what we need, they are different in that they do not specify the conditions for which those groups should be enabled or not.
Conditional validation proposal
We proposed a framework for conditional validation which provides several types of conditions:
-
Unified Expression Language (UEL) conditions
-
Validation Annotations conditions
-
Programmatic conditions
Validation conditions are referred to by name in validation constraints
that want to depend on those conditions. For example, the
@NotNull(validationConditions={"admin"})
validation constraint is only
enabled iff the admin
validation condition is true.
Validation condition on boolean value
Validation conditions are then defined in various flavours, depending on
the condition they are checking. The most simple example is the
@ValidationConditionOnTrue
which defines a validation condition on a
bean property which evaluates to true
:
@ValidationConditionOnTrue
public class User {
// simple check for admin user
@ValidationConditionOnTrue(name = "admin")
private boolean admin;
// sometimes it can be more complicated
private List<Permission> permissions = new ArrayList<Permission>();
@ValidationConditionOnTrue(name = "admin")
public boolean hasAdminPermission(){
for(Permission p : permissions)
if(p.isAdmin())
return true;
return false;
}
// now that we have defined two ways that the validation condition "admin"
// could be true, we can use it
// we require administrators to have a valid email address. other users may
// have one too, but it is not required.
@NotNull(validationConditions = {"admin"})
@Email
private String emailAddress;
}
Validation condition on expression
Now that we are familiar with the difference between referencing and
defining a validation condition, let us look at more complex validation
conditions, such as the @ValidationConditionOnUEL
which defines a
validation condition based on a
Unified
Expression Language (UEL) expression. This is very useful for checks on
properties located anywhere within the bean, multiple properties, or
even sub-properties. With this we can accomplish our minimum requirement
of having at least one of the postAddress
or emailAddress
properties
set:
@ValidationConditionOnUEL
public class User {
// we want emailAddress to be set if postAddress isn't set
@NotNull(validationConditions = {"noPostAddress"})
@Email
// define a validation condition true if emailAddress is not set
@ValidationConditionOnUEL(name = "noEmailAddress", uel = "emailAddress == null")
private String emailAddress;
// we want postAddress to be set if emailAddress isn't set
@NotNull(validationConditions = {"noEmailAddress"})
@Valid
// define a validation condition true if postAddress is not set
@ValidationConditionOnUEL(name = "noPostAddress", uel = "postAddress == null")
private Address postAddress;
}
Note that since the UEL expression has access to the whole bean, it does not matter really whether the validation condition is placed on a property or on the bean itself. It is simply a matter of style and we prefer to have them located near their source, but naturally, should an UEL expression reference several properties, we would place the validation condition definition on the bean for clarity.
Validation condition on other validators
We still have one more type of validation condition to see: a validation
condition which depends on the success or failure of other validation.
In the previous example, we’ve used null
checks in UEL, but we also
defined @NotNull
validation constraints. We would like to be able to
reuse those constraints so say "validation this property iff this
property failed to validate, or succeeded in validating". Because Java
Annotations are so limited, we must resort to referencing those
validation constraints by name, which we then must assign. Let us
redefine the previous example with this new validation condition:
@ValidationConditionOnValue
public class User {
// we want emailAddress to be set if postAddress isn't set
@NotNull(name = "nullEmailAddress", validationConditions = {"noPostAddress"})
@Email
// define a validation condition true if nullPostAddress fails to validate
@ValidationConditionOnValue(name = "noEmailAddress",
failedValidators = {"nullPostAddress"})
private String emailAddress;
// we want postAddress to be set if emailAddress isn't set
@NotNull(name = "nullPostAddress", validationConditions = {"noEmailAddress"})
@Valid
// define a validation condition true if nullEmailAddress fails to validate
@ValidationConditionOnValue(name = "noPostAddress",
failedValidators = {"nullEmailAddress"})
private Address postAddress;
}
Using this last type of validation condition we can even define some dependency: validation which does not make sense if a required validator already failed. In the complex validation required by postal addresses, we want to validate the country code and the post code. But the post code depends on the street and country code, so in order for its validation to be meaningful, we must make sure that the street and country codes have already been validated:
@ValidationConditionOnValue dependency
public class Address {
@NotEmpty(name = "setStreet")
private String street;
@NotNull(name = "setCountryCode")
@ValidCountryCode(name = "validCountryCode")
private String countryCode;
@NotNull
@ValidPostCode(validationConditions = {"validStreetAndCountryCode"})
@ValidationConditionOnValue(name = "validStreetAndCountryCode",
validators = {"setStreet",
"setCountryCode",
"validCountryCode"})
private String postCode;
}
Note that this previous example only makes sense if Validator
is fixed
so as to include the whole bean instance even for property validation,
otherwise the @ValidPostCode
cannot access the street
and
countryCode
properties.
Also note that while we would like in some cases to actually include the
validation condition’s definition in the validation constraints
themselves (for instance @NotNull(uel = "otherProperty == null")
), it
is probably better practice to differentiate condition validation
references and definitions so that one definition can be reused in
multiple references. Due to the lack of inheritance in annotations, all
validation annotations will be required to support two attributes more
than the current message
attribute:
String[] validationConditions : default {
} and
String name : default ""
. This is unfortunate, but we don’t see any
better alternative.
Validation Events
Sometimes it is not enough to support these conditions, and we would
like to condition the validation based on some external event outside
the scope of the bean itself. For instance, in JPA — which is where
Hibernate Validation is applied right now —, we would often like to
validate a bean when it is inserted, updated, or before it is deleted.
This would then be based on JPA events such as ON_INSERT
, ON_UPDATE
,
ON_DELETE
corresponding to the underlying operation done on the entity
bean.
Sometimes we also have expensive validation that we would like to only
trigger when its costs has to be paid: when the value being checked has
changed. For instance when we want to validate an entire collection of
entities, we would like to only validate it when that collection has
changed. Or to get back to our previous example of postal address
validation, we would want to validate the whole address only if any of
its property has changed. This can be seen as an additional event in JPA
which we can name ON_CHANGE
. The validation framework would then be
responsible for checking whether the persisted value differs from the
validated value, and trigger the validation only if they differ.
Validation Events
@ValidationConditionOnEvent(name = "changed", events = {ValidationEvent.ON_CHANGE})
public class Address {
@NotEmpty(name = "setStreet", validationConditions = {"changed"})
private String street;
@NotNull(name = "setCountryCode", validationConditions = {"changed"})
@ValidCountryCode(name = "validCountryCode", validationConditions = {"changed"})
private String countryCode;
@NotNull(validationConditions = {"changed"})
@ValidPostCode(validationConditions = {"validStreetAndCountryCode", "changed"})
@ValidationConditionOnValue(name = "validStreetAndCountryCode",
validators = {"setStreet",
"setCountryCode",
"validCountryCode"})
private String postCode;
}
We can see from this previous example that we might need some sort of default validation group which would apply to the whole bean or each validation constraint.
Here is an other illustration:
Validation Events 2
// define a condition true when we are going to delete this entity
@ValidationConditionOnEvent(name = "deleted", events = {ValidationEvent.ON_DELETE})
public class User {
// we want to pay the expensive validation of this collection only when it changes
@CheckJobs(validationConditions = {"changedJobs"})
@ValidationConditionOnEvent(name = "changedJobs", events = {ValidationEvent.ON_CHANGE})
private List<Job> runningJobs = new ArrayList<Job>();
// we can only delete this entity if it has no more running jobs
@AssertTrue(validationConditions = {"deleted"})
public boolean hasNoRunningJobs(){
return runningJobs.size() == 0;
}
}
Semantics
Basically because a validation check (running a validator’s isValid
)
method should be side-effect-free, we can take the simplistic view that
during validation we can run every validator, and once we have
determined which ones failed with what error messages, and which ones
passed, we then proceed to check whether the failed ones were
meaningful. They are meaningful if their validation conditions evaluate
to true. They could all be resolved at this phase (including the tricky
@ValidationConditionOnValue
).
In order to be more efficient though, we should attempt to only run
validators whose validation conditions evaluate to true. This is very
straightforward in the cases of @ValidationConditionOnTrue
,
@ValidationConditionOnUEL
and @ValidationConditionOnEvent
. For
@ValidationConditionOnValue
, which depends on other validators'
success or failure, we can attempt to run the referenced validators
lazily, unless we get into a dependency loop (which is valid: look at
our previous example of requiring at least one of email or postal
addresses), in which case we can start resolving the loop by running
validators as described previously, since they are supposed to be
without side-effect, and validation failures are only meaningful if they
pass the validation condition later on.
Implementing @ValidationConditionOnEvent
would required a pluggable
validation condition from JPA, which could instruct us of the current
operation on the bean (insert, update, delete) that triggered the
validation. The ON_CHANGE
condition event would simply fetch the value
currently persisted and use Java equality to check for any change in the
value.
JPA 2.0 (JSR-317)
JPA is set for another major revision through the JSR-317. We can only hope that some consistency will be included with Bean Validation (JSR-303) so that our gripes with unicity constraints as well as clear semantics for validation exceptions will be resolved.
[Hibernatevalidation-foot1]#
1: I really wonder why the
Annotation
reification (reflection) of the annotation instance does not include
pointers to the annotated object such as its type and value. Perhaps a
question to ask the new
annotation JSR project.
About the author
Stéphane
Épardaud is a senior software developer at Lunatech Research. Although
comments are disabled on this blog, he encourages you to send him
comments by mail, corrections as well as opinions. Feedback is valued.
_