【问题标题】:How to use Hibernate validation annotations with enums?如何使用带有枚举的 Hibernate 验证注释?
【发布时间】:2013-08-14 20:58:42
【问题描述】:

如何使用休眠注释来验证枚举成员字段? 以下方法不起作用:

enum UserRole {
   USER, ADMIN;
}

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

【问题讨论】:

标签: java hibernate annotations


【解决方案1】:

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

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”不是有效的用户类型。有效的用户类型 值是:人员、公司


编辑

对于那些想要非 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://stackoverflow.com/a/19833921/82609

【讨论】:

  • 如果没有 guava 函数怎么写?
  • @membersound 真的一点也不复杂。您只需将枚举数组(enumSelected.getEnumConstants() 转换为一组枚举名称(AVAILABLE_ENUM_NAMES),您就可以轻松找到使用 Java 的教程 :) 参见此处stackoverflow.com/questions/13783295/…
  • 好的对不起,我可能误解了:我认为可以创建一个通用的默认错误消息,并在里面打印所有的通用枚举值。,
  • @membersound 是的,您理解正确:可以使用我的代码打印所有可用的枚举值。它与 Guava 无关,Guava 只是我用来实现此目的的一些实用程序,但您也可以使用计划旧 Java。我已经更新了答案
  • 如果您希望原始帖子中的“标题”字段实际上是枚举类型怎么办?为什么不使用枚举作为字段类型?这就是我现在的情况,我不知道如何验证传递给我的 JSON 的字符串是枚举值之一,并像其他 Spring/Hibernate 验证错误一样正确捕获错误。有什么建议吗?
【解决方案2】:

@NotBlank

验证带注释的字符串不为 null 或为空。与 NotEmpty 的不同之处在于尾随空格会被忽略。

其中UserRole 不是字符串和object 使用@NotNull

带注释的元素不能为空。接受任何类型。

【讨论】:

    【解决方案3】:

    我认为与上面的Sebastien's answer 更密切相关,代码行更少,并使用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 设置并使用默认注释。

    【讨论】:

    【解决方案4】:

    通常,尝试转换为枚举不仅仅是通过名称(这是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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-05
      • 1970-01-01
      • 2012-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-19
      • 1970-01-01
      相关资源
      最近更新 更多