How to validate field level constraint before class level constraint?

后端 未结 2 1021
抹茶落季
抹茶落季 2021-01-06 23:36

I have a class:

@ColumnNameUnique(groups = CreateTableChecks.class)
public class Table {    
    @Valid
    @NotEmpty(groups = CreateTableChecks.class)
    p         


        
相关标签:
2条回答
  • 2021-01-06 23:57

    Instead of using @Hardy mentioned solution with @GroupSequence you can validate fields manually using reflection before your Validator.validate call.

    Method

    You can wrap this method

    /**
     * Validates all single constrained fields of the given object and returns a
     * set of {@link ConstraintViolation}. If <code>first</code> is
     * <code>true</code> only the ConstraintViolation of the first invalid
     * constraint is returned. <br>
     * This method is useful to validate property constraints before class level
     * constraints.
     *
     * @param validator
     * @param object
     * @param first Set to <code>true</code> if only the exceptions of the first
     *            invalid property shall be thrown
     * @param groups
     */
    public static Set<ConstraintViolation<Object>> validateProperties(final Validator validator, final Object object,
        final boolean first, final Class<?>... groups)
    {
        if (object == null)
            throw new IllegalArgumentException("object must not be null.");
        if (validator == null)
            throw new IllegalArgumentException("validator must not be null.");
    
        final Set<ConstraintViolation<Object>> cvs = new HashSet<>();
    
        forFields: for (final Field field : ReflectionUtils.getAllFields(object.getClass(), null))
        {
            final Annotation[] annotations = field.getDeclaredAnnotations();
    
            boolean hasValidAnnotation = false;
    
            for (final Annotation annotation : annotations)
            {
                // single Constraint
                final Constraint constraint = annotation.annotationType().getAnnotation(Constraint.class);
                if (constraint != null)
                {
                    cvs.addAll(validator.validateProperty(object, field.getName(), groups));
    
                    if (!cvs.isEmpty() && first)
                        break forFields;
                }
    
                if (annotation.annotationType().equals(Valid.class))
                    hasValidAnnotation = true;
            }
    
            // nested validation
            if (hasValidAnnotation)
            {
                field.setAccessible(true);
                Object value = null;
                try
                {
                    value = field.get(object);
                }
                catch (IllegalArgumentException | IllegalAccessException e)
                {
                    // log
                }
    
                if (value != null)
                {
                    cvs.addAll(validateProperties(validator, value, first, groups));
    
                    if (!cvs.isEmpty() && first)
                        break;
    
                }
            }
        }
    
        return cvs;
    }
    
    /**
     * Validates all single constrained fields of the given object and throws a
     * {@link ConstraintViolationException}. If <code>first</code> is
     * <code>true</code> only the ConstraintViolation of the first invalid
     * constraint is thrown. <br>
     * <br>
     * This method is useful to validate property constraints before class level
     * constraints.
     *
     * https://hibernate.atlassian.net/browse/BVAL-557
     *
     * @see #validateProperty(Validator, Object, String, Class...)
     *
     * @param validator
     * @param object
     * @param first Set to <code>true</code> if only the exceptions of the first
     *            invalid property shall be thrown
     * @param groups
     *
     * @throws ConstraintViolationException
     */
    public static void validatePropertiesThrow(final Validator validator, final Object object, final boolean first,
        final Class<?>... groups) throws ConstraintViolationException
    {
        if (object == null)
            throw new IllegalArgumentException("object must not be null.");
        if (validator == null)
            throw new IllegalArgumentException("validator must not be null.");
    
        final Set<ConstraintViolation<Object>> cvs = validateProperties(validator, object, first,
            groups);
    
        if (!cvs.isEmpty())
            throw new ConstraintViolationException(cvs);
    }
    

    I prefer this approach since i do not want to to update all our entities and fields with group sequence annotations.

    0 讨论(0)
  • 2021-01-07 00:12

    You need to use @GroupSequence and re-define the default group sequence. Without this the validation order within a group is not defined and it can be in any order (that the class level constraint in your case is always executed first is not a must). Something like this should work:

    @GroupSequence({FieldChecks.class, ClassChecks.class})
    @ColumnNameUnique(groups = ClassChecks.class)
    public class Table {    
        @Valid
        @NotEmpty(groups = FieldChecks.class)
        private List<Measure> measures; 
    }
    

    Now, if the @Default group gets validated, first the class level constraints and then the field level ones will be validated.

    0 讨论(0)
提交回复
热议问题