如何在枚举中使用Hibernate验证注释?

20

我如何使用Hibernate注释来验证枚举成员字段? 以下内容不起作用:

enum UserRole {
   USER, ADMIN;
}

class User {
   @NotBlank //HV000030: No validator could be found for type: UserRole.
   UserRole userRole;
}

1
你尝试过使用@Nonnull吗? - Suresh Atta
1
这是一个非常好的、直截了当的教程,涉及此主题:枚举类型的验证 - informatik01
4个回答

42

请注意,您还可以创建一个验证器来检查字符串是否是枚举的一部分。

public enum UserType { PERSON, COMPANY }

@NotNull
@StringEnumeration(enumClass = UserCivility.class)
private String title;

@Documented
@Constraint(validatedBy = StringEnumerationValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, CONSTRUCTOR })
@Retention(RUNTIME)
public @interface StringEnumeration {

  String message() default "{com.xxx.bean.validation.constraints.StringEnumeration.message}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};

  Class<? extends Enum<?>> enumClass();

}

public class StringEnumerationValidator implements ConstraintValidator<StringEnumeration, String> {

  private Set<String> AVAILABLE_ENUM_NAMES;

  @Override
  public void initialize(StringEnumeration stringEnumeration) {
    Class<? extends Enum<?>> enumSelected = stringEnumeration.enumClass();
    //Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumSelected);
    Set<? extends Enum<?>> enumInstances = Sets.newHashSet(enumSelected.getEnumConstants());
    AVAILABLE_ENUM_NAMES = FluentIterable
            .from(enumInstances)
            .transform(PrimitiveGuavaFunctions.ENUM_TO_NAME)
            .toSet();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    if ( value == null ) {
      return true;
    } else {
      return AVAILABLE_ENUM_NAMES.contains(value);
    }
  }

}

这很好,因为您不会丢失“错误值”的信息。 您可以获得像以下消息:

价值“someBadUserType”不是有效的UserType。有效的UserType值为:PERSON,COMPANY


编辑

对于那些想要非Guava版本的人,它应该可以使用类似以下的东西:

public class StringEnumerationValidator implements ConstraintValidator<StringEnumeration, String> {

  private Set<String> AVAILABLE_ENUM_NAMES;

  public static Set<String> getNamesSet(Class<? extends Enum<?>> e) {
     Enum<?>[] enums = e.getEnumConstants();
     String[] names = new String[enums.length];
     for (int i = 0; i < enums.length; i++) {
         names[i] = enums[i].name();
     }
     Set<String> mySet = new HashSet<String>(Arrays.asList(names));
     return mySet;
  }

  @Override
  public void initialize(StringEnumeration stringEnumeration) {
    Class<? extends Enum<?>> enumSelected = stringEnumeration.enumClass();
    AVAILABLE_ENUM_NAMES = getNamesSet(enumSelected);
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    if ( value == null ) {
      return true;
    } else {
      return AVAILABLE_ENUM_NAMES.contains(value);
    }
  }

}

如果想要自定义错误消息并展示适当的值,请参考此链接:https://dev59.com/Ueo6XIcBkEYKwwoYLhPO#19833921


1
@membersound,实际上这并不复杂。你只需要将一个枚举数组(enumSelected.getEnumConstants())转换为一个枚举名称的集合(AVAILABLE_ENUM_NAMES),然后你就可以很容易地找到使用Java完成此操作的教程了 :) 可以参考这里:https://dev59.com/kWYr5IYBdhLWcg3wTYgQ - Sebastien Lorber
1
@membersound 是的,你理解得很正确:使用我的代码可以打印出所有可用的枚举值。这与Guava无关,Guava只是我用来实现这一点的一些工具,但你也可以使用普通的Java。我已经更新了我的答案。 - Sebastien Lorber
@SebastienLorber 我喜欢这个解决方案!但是,我该如何更改验证错误消息以包含有效的值? - Robin Jonsson
@RobinJonsson 我不再使用验证了,但是也许你可以尝试一下 @StringEnumeration(enumClass = UserCivility.class, message="my custom message key") - Sebastien Lorber
什么是UserCivility.class,它是如何构建的? - amdev
显示剩余6条评论

13

@NotBlank

验证被注释的字符串不为null或空。与NotEmpty的区别在于忽略尾随的空格。

UserRole不是String类型,而是一个对象,因此请使用@NotNull

被注释的元素不能为null。接受任何类型。


8

我认为与Sebastien的答案更相关的是,代码行数更少,并且利用EnumSet.allOf来代替rawtypes警告。

枚举设置

public enum FuelTypeEnum {DIESEL, PETROL, ELECTRIC, HYBRID, ...}; 
public enum BodyTypeEnum {VAN, COUPE, MUV, JEEP, ...}; 

注释设置

@Target(ElementType.FIELD) //METHOD, CONSTRUCTOR, etc.
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
public @interface ValidateEnum {
    String message() default "{com.xxx.yyy.ValidateEnum.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Class<? extends Enum<?>> targetClassType();
}

验证器设置

public class EnumValidator implements ConstraintValidator<ValidateEnum, String> {
    private Set<String> allowedValues;

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void initialize(ValidateEnum targetEnum) {
        Class<? extends Enum> enumSelected = targetEnum.targetClassType();
        allowedValues = (Set<String>) EnumSet.allOf(enumSelected).stream().map(e -> ((Enum<? extends Enum<?>>) e).name())
                .collect(Collectors.toSet());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || allowedValues.contains(value)? true : false; 
    }
} 

现在,请按照以下方式注释您的字段。
@ValidateEnum(targetClassType = FuelTypeEnum.class, message = "Please select ...." 
private String fuelType; 

@ValidateEnum(targetClassType = BodyTypeEnum.class, message = "Please select ...." 
private String bodyType; 

上述假设您已经设置并使用默认注释使 Hibernate Validator 工作。

我在这里分享了一个非Hibernate验证器的解决方案,链接为https://dev59.com/910b5IYBdhLWcg3wM-4c#59440957,其中使用了Apache的EnumUtils。 - Raf

2
往往情况下,尝试转换为枚举类型不仅仅是通过名称(这是使用valueOf方法的默认行为)。例如,如果您有表示DayOfWeek的枚举,并且您想将整数转换为DayOfWeek,那么可以创建以下注释:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = {ValidEnumValueValidator.class})
public @interface ValidEnumValue {
    String message() default "invalidParam";

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

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

    Class<? extends Enum<?>> value();

    String enumMethod() default "name";

    String stringMethod() default "toString";
}

public class ValidEnumValueValidator implements ConstraintValidator<ValidEnumValue, String> {
    Class<? extends Enum<?>> enumClass;
    String enumMethod;
    String stringMethod;

    @Override
    public void initialize(ValidEnumValue annotation) {
        this.enumClass = annotation.value();
        this.enumMethod = annotation.enumMethod();
        this.stringMethod = annotation.stringMethod();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        Enum<?>[] enums = enumClass.getEnumConstants();
        Method method = ReflectionUtils.findMethod(enumClass, enumMethod);

        return Objects.nonNull(enums) && Arrays.stream(enums)
                .map(en -> ReflectionUtils.invokeMethod(method, en))
                .anyMatch(en -> {
                    Method m = ReflectionUtils.findMethod(String.class, stringMethod);
                    Object o = ReflectionUtils.invokeMethod(m, value);

                    return Objects.equals(o, en);
                });
    }
}

您可以按以下方式使用它:
public enum TestEnum {
        A("test");

        TestEnum(String s) {
            this.val = s;
        }

        private String val;

        public String getValue() {
            return this.val;
        }
    }

public static class Testee {
        @ValidEnumValue(value = TestEnum.class, enumMethod = "getValue", stringMethod = "toLowerCase")
        private String testEnum;
    }

上述实现使用了Spring框架和Java 8+中的ReflectionUtils

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接