EntityA has many related EntityB. EntityB can only be related to EntityA if field value matches:
@Entity
public class EntityA {
@Id
@GeneratedValue
public Long id;
@NotNull
public String field;
@NotNull
@OneToMany(mappedBy = "entityA", cascade = CascadeType.ALL)
public List<@Valid EntityB> entityB;
}
@EntityBConstraint
@Entity
public class EntityB {
@Id
@GeneratedValue
public Long id;
@NotNull
public String field;
@NotNull
@ManyToOne
public EntityA entityA;
}
public class EntityBValidator implements ConstraintValidator<EntityBConstraint, EntityB> {
@Override
public boolean isValid(EntityB entityB, ConstraintValidatorContext constraintValidatorContext) {
return entityB.field.equals(entityB.entityA.field);
}
}
Only changed (dirty) entities get validated when interacting with the EntityManager. This is not obivous and well documented - at least we could not find it.
How would you handle this without manually calling Validator.validate?
If the relation is bidirectional like in the example above you could define the class level constraint on both sides?
But what to do with unidirectional relations?
@EntityBConstraint
@Entity
public class EntityB {
@Id
@GeneratedValue
public Long id;
@NotNull
public String field;
@Valid
@ManyToOne(cascade = CascadeType.ALL)
public EntityC entityC;
}
@Entity
public class EntityC {
@Id
@GeneratedValue
public Long id;
public String field;
}
public class EntityBValidator implements ConstraintValidator<EntityBConstraint, EntityB> {
@Override
public boolean isValid(EntityB entityB, ConstraintValidatorContext constraintValidatorContext) {
return entityB.field.equals(entityB.entityC.field);
}
}
A potential EntityCValidator could not access EntityB..
It’s about collections, yeah, just like in your example. In general, right now Hibernate ORM validates only objects that changed. Ideally, it would validate the “owning entity” of objects that changed i.e. if an embeddable, element collection or owned collection changes, that would trigger validation of the owning entity.
An entity is not affected by changes to associated entities or unowned collections, so that would still not trigger validation. If you have a @Version field, you could mark these associations with @OptimisticLock(excluded = false) to make sure that changes to them cause a version increment and hence mark the entity dirty, to trigger validation through that process.
Update does still no fail (no validation of EntityB) and entityB.version is not incremented.
My point is that if you have a EntityA with @Valid-annotated relations and EntityA gets validated with ORM, these relations will be ignored. So EntityA is by bean validation definition not valid.
In other words, there’s a common misconception that entities validated by the ORM are automatically valid according to the Bean Validation specification.
Validation cascade (@Valid) must not occur for entity associations (single- or multi-valued).
Also, the JPA spec explains that validation callbacks happen through entity lifecycle callbacks, so if an entity is not updated, then the validation for that entity won’t happen. So if you want a change in an EntityB instance to trigger re-validation of EntityA, then you will have to update some timestamp in the EntityA instance to force an update.