Аннотации проверки гибернации - подтвердите, что хотя бы одно поле не равно нулю

Есть ли способ определить правило проверки Hibernate с помощью аннотаций, как указано здесь, указав, что хотя бы одно поле должно быть не нулевым?

Это будет гипотетический пример (@OneFieldMustBeNotNullConstraint на самом деле не существует):

@Entity
@OneFieldMustBeNotNullConstraint(list={fieldA,fieldB})
public class Card {

    @Id
    @GeneratedValue
    private Integer card_id;

    @Column(nullable = true)
    private Long fieldA;

    @Column(nullable = true)
    private Long fieldB;

}

В проиллюстрированном случае fieldA может быть нулевым, или поле B может быть нулевым, но не тем и другим.

Один из способов - создать мой собственный валидатор, но я бы хотел избежать, если он уже существует. Пожалуйста, поделитесь одним валидатором, если у вас уже есть... спасибо!

Ответ 1

Наконец, я написал весь валидатор:

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;

import org.apache.commons.beanutils.PropertyUtils; 

@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckAtLeastOneNotNull.CheckAtLeastOneNotNullValidator.class)
@Documented
public @interface CheckAtLeastOneNotNull {

     String message() default "{com.xxx.constraints.checkatleastnotnull}";

        Class<?>[] groups() default {};

        Class<? extends Payload>[] payload() default {};

        String[] fieldNames();

        public static class CheckAtLeastOneNotNullValidator implements ConstraintValidator<CheckAtLeastOneNotNull, Object> {

            private String[] fieldNames;

            public void initialize(CheckAtLeastOneNotNull constraintAnnotation) {
                this.fieldNames = constraintAnnotation.fieldNames();
            }

            public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {


                if (object == null)
                    return true;

                try {

                    for (String fieldName:fieldNames){
                        Object property = PropertyUtils.getProperty(object, fieldName);

                        if (property!=null) return true;
                    }

                    return false;

                } catch (Exception e) {
                   System.printStackTrace(e);   
                    return false;
                }
            }

        }

}

Пример использования:

@Entity
@CheckAtLeastOneNotNull(fieldNames={"fieldA","fieldB"})
public class Reward {

    @Id
    @GeneratedValue
    private Integer id;

    private Integer fieldA;
    private Integer fieldB;

    [...] // accessors, other fields, etc.
}

Ответ 2

Просто напишите свой собственный валидатор. Не должно быть довольно просто: перебрать имена полей и получить значения полей с помощью отражения.

Концепция:

Collection<String> values = Arrays.asList(
    BeanUtils.getProperty(obj, fieldA),
    BeanUtils.getProperty(obj, fieldB),
);

return CollectionUtils.exists(values, PredicateUtils.notNullPredicate());

Там я использовал методы из commons-beanutils и commons-collections.

Ответ 3

Это немного похоже на ответ Resh32, но это также связывает сообщение проверки с конкретным полем объекта проверки.

Класс аннотации проверки будет похож на следующий.

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author rumman
 * @since 9/23/19
 */
@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = NotNullAnyValidator.class)
public @interface NotNullAny {

    String[] fieldNames();

    String errorOnProperty();

    String messageKey() default "{error.required}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Класс валидатора будет выглядеть следующим образом.

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Objects;

import static org.springframework.beans.BeanUtils.getPropertyDescriptor;

/**
 * @author rumman
 * @since 9/23/19
 */
public class NotNullAnyValidator implements ConstraintValidator<NotNullAny, Object> {

    private String[] fieldNames;
    private String errorOnProperty;
    private String messageKey;

    @Override
    public void initialize(NotNullAny validateDateRange) {
        fieldNames = validateDateRange.fieldNames();
        errorOnProperty = validateDateRange.errorOnProperty();
        messageKey = validateDateRange.messageKey();
    }

    @Override
    public boolean isValid(Object obj, ConstraintValidatorContext validatorContext) {

        Object[] fieldValues = new Object[fieldNames.length];

        try {
            for (int i = 0; i < fieldValues.length; i++) {
                fieldValues[i] = getPropertyDescriptor(obj.getClass(), fieldNames[i]).getReadMethod().invoke(obj);
            }

        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        if (Arrays.stream(fieldValues).noneMatch(Objects::nonNull)) {
            validatorContext.buildConstraintViolationWithTemplate(messageKey)
                    .addPropertyNode(errorOnProperty)
                    .addConstraintViolation()
                    .disableDefaultConstraintViolation();

            return false;
        }

        return true;
    }
}

Обратите внимание на последний блок условия if, он проверяет, не найдено ли не значение null, затем указывает сообщение об ошибке, свойство, с которым будет связано сообщение об ошибке, и добавит нарушение ограничения.

Использовать аннотацию в классе

/**
 * @author rumman
 * @since 9/23/19
 */
@NotNullAny(fieldNames = {"field1", "field2", "field3"},
        errorOnProperty = "field1",
        messageKey = "my.error.msg.key")
public class TestEntityForValidation {

    private String field1;
    private String field2;
    private String field3;

    // standard constructor(s) and getter & setters below
}