I have an entity like this:
@Entity
@Table(name = "transaction_receiver")
public class TransactionReceiver implements Serializable, Addressable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotNull
@Column(name = "contact_id", nullable = false)
private String contactId;
@Column(name = "datev_number")
private String datevNumber;
@NotNull
@Column(name = "receiver", nullable = false)
private String receiver;
@NotNull
@Size(min = 22, max = 34)
@Column(name = "iban", length = 34, nullable = false)
private String iban;
@Size(min = 8, max = 11)
@Column(name = "bic", length = 11, nullable = false)
private String bic;
@NotNull
@Column(name = "institute")
private String institute;
@Column(name = "company")
private String company;
I need to write a custom validation "you can provide an empty iban, bic and institute, that's ok. But if any of the fields is not empty, the above constraint have to hold"
I am looking for the most elegant way to accomplish this.
My current solution is - what I think somehow dirty, but working - to use a @PrePersist
statement and throw exceptions from there
@PrePersist
public void checkBankData() {
boolean ibanEmpty = iban == null || iban.isEmpty();
boolean ibanValid = !ibanEmpty && iban.length() >= 22 && iban.length() <= 34;
boolean bicEmpty = bic == null || bic.isEmpty();
boolean bicValid = !bicEmpty && bic.length() >= 8 && bic.length() <= 11;
boolean instituteEmpty = institute == null || institute.isEmpty();
boolean validState = (ibanEmpty && bicEmpty && instituteEmpty) || ibanValid && bicValid;
if (!validState) {
throw new IllegalStateException(
String.format(
"bank data is not empty and %s%s%s%s%s",
!ibanValid ? "iban has to be from 22 to 34 chars long" : "",
!ibanValid && !bicValid ? "and" : "",
!bicValid ? "bic has to be from 8 to 11 chars long" : "",
!ibanValid && !bicValid && instituteEmpty ? "and" : "",
instituteEmpty ? "institue must not be empty" : ""
)
);
}
}
Which isn't subject to @Valid
annotations. A different approach would be defining a custom validator like described here: http://docs.jboss.org/hibernate/validator/4.1/reference/en-US/html/validator-customconstraints.html
But this really looks like an overkill for my constraint.
Isn't there any other elegant way?
Using Hibernate Validation API is not as complex as it seems, and for your constraint is a nice solution. However you can get a easier way to define constrains using Hibernate Validator as we have done in one project adding a few classes. Your constraints will look like this:
@Validate(method = "checkBankData", message = "{BankData.invalid.message}")
@Entity
@Table(name = "transaction_receiver")
public class TransactionReceiver implements Serializable, Addressable {
To get this you need to define @Validate annotation and a CustomValidator class.
@Target({ ElementType.TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CustomValidator.class)
@Documented
/**
* Annotation to allow custom validation against model classes
*/
public @interface Validate {
/**
* Validation message
*/
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Validation method name
*/
String method() default "";
}
public class CustomValidator implements ConstraintValidator<Validate, BusinessObject> {
private static Log log = LogFactory.getLog(CustomValidator.class);
private String validator;
@Override
public void initialize(Validate constraintAnnotation) {
validator = constraintAnnotation.method();
}
@Override
public boolean isValid(BusinessObject bo, ConstraintValidatorContext constraintContext) {
try {
return isValidForMethod(bo);
} catch (Exception e) {
/* Error durante la ejecución de la condición o del validador */
log.error("Error validating "+bo, e);
return false;
}
}
private boolean isValidForMethod(BusinessObject bo) throws Exception {
Method validatorMethod = ReflectionUtils.findMethod(bo.getClass(), validator, new Class[] {});
if (validatorMethod != null) {
/* Validator call */
Boolean valid = (Boolean) validatorMethod.invoke(bo);
return valid != null && valid;
} else {
/* Method not found */
log.error("Validator method not found.");
return false;
}
}
}
This aproach will be nice if you plan to define more constraints. And you can extend it with more features like conditions for validation or adding multiple validations, etc.
Off-topic:
Validation has nothing to do with Spring Boot so there is no need to mention it in your question.
serialVersionUID = 1L; Is a very bad idea. Use your IDE serialVersionUID generator to fill this field with a value different for 1L.