Validating double and float values using Hibernate

2019-02-06 13:27发布

问题:

I'm looking for a way to validate a java.lang.Double field in the Spring command bean for its maximum and minimum values (a value must lie between a given range of values) like,

public final class WeightBean
{
     @Max(groups={ValidationGroup.class}, value=Double.MAX_VALUE, message="some key or default message")
     @Min(groups={ValidationGroup.class}, value=1D, message="some key or default message")
     private Double txtWeight;  //Getter and setter.

     public interface ValidationGroup{}         
}

But both @Max and @Min cannot take a java.lang.Double value.

Note that double and float are not supported due to rounding errors (some providers might provide some approximative support)

So what is the way of validating such fields?

I'm working with Spring 3.2.0 and Hibernate Validator 4.3.1 CR1.

回答1:

You can use the annotation, but you might get false results depending. This is a general problem with doubles and imo in many cases _Double_s should be avoided. Maybe switching to a different type is the best solution? BigDecimal for example?



回答2:

If you have switched to BigDecimal (or BigInteger), you could use @DecimalMin or @DecimalMax. But this is still no solution for float or double.



回答3:

I have avoided the double and the float types and implemented a custom validator that could validate a BigDecimal value based on the precision and the scale.

The constraint descriptor.

package constraintdescriptor;

import constraintvalidator.BigDecimalRangeValidator;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = BigDecimalRangeValidator.class)
@Documented
public @interface BigDecimalRange {
    public String message() default "{java.math.BigDecimal.range.error}";
    public Class<?>[] groups() default {};
    public Class<? extends Payload>[] payload() default {};

    long minPrecision() default Long.MIN_VALUE;
    long maxPrecision() default Long.MAX_VALUE;
    int scale() default 0;
}

The constraint validator.

package constraintvalidator;

import constraintdescriptor.BigDecimalRange;
import java.math.BigDecimal;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public final class BigDecimalRangeValidator implements ConstraintValidator<BigDecimalRange, Object> {

    private long maxPrecision;
    private long minPrecision;
    private int scale;

    @Override
    public void initialize(final BigDecimalRange bigDecimalRange) {
        maxPrecision = bigDecimalRange.maxPrecision();
        minPrecision = bigDecimalRange.minPrecision();
        scale = bigDecimalRange.scale();
    }

    @Override
    public boolean isValid(final Object object, final ConstraintValidatorContext cvc) {
        boolean isValid = false;

        if (object == null) { // This should be validated by the not null validator (@NotNull).
            isValid = true;
        } else if (object instanceof BigDecimal) {
            BigDecimal bigDecimal = new BigDecimal(object.toString());
            int actualPrecision = bigDecimal.precision();
            int actualScale = bigDecimal.scale();
            isValid = actualPrecision >= minPrecision && actualPrecision <= maxPrecision && actualScale <= scale;

            if (!isValid) {
                cvc.disableDefaultConstraintViolation();
                cvc.buildConstraintViolationWithTemplate("Precision expected (minimun : " + minPrecision + ", maximum : " + maxPrecision + "). Maximum scale expected : " + scale + ". Found precision : " + actualPrecision + ", scale : " + actualScale).addConstraintViolation();
            }
        }

        return isValid;
    }
}

This could be extended for other types as well, as and when required.


And finally in the bean, the property of the type BigDecimal could be annotated by the @BigDecimalRange annotation as follows.

package validatorbeans;

public final class WeightBean {

    @BigDecimalRange(minPrecision = 1, maxPrecision = 33, scale = 2, groups = {ValidationGroup.class}, message = "The precision and the scale should be less than or equal to 35 and 2 respectively.")
    private BigDecimal txtWeight; // Getter and setter.

    public interface ValidationGroup {}
}


回答4:

Sometimes it's convenient in pair with @AssertTrue / @AssertFalse from javax.validation.constraints

public final class WeightBean {
    @NotNull
    private Double txtWeight;  //Getter and setter.

    @AssertTrue
    public boolean getTxtWeightCheck() {
        return txtWeight > 0.1 && txtWeight < 0.9;
    }
}


回答5:

You can also use @Digits from the hibernate validator API as well

@Digits(integer = 10 /*precision*/, fraction = 2 /*scale*/)