java.lang.IllegalArgumentException:没有枚举 BusinessCustomersStatus.4d29e059cf

java.lang.IllegalArgumentException: No enum BusinessCustomersStatus.4d29e059cf

我想使用 ENUM 值创建搜索规范:

搜索枚举:

public enum BusinessCustomersStatus {
    A("active"),
    O("onboarding"),
    N("not_verified"),
    V("verified"),
    S("suspended"),
    I("inactive");

    private String status;

    BusinessCustomersStatus(String status)
    {
        this.status = status;
    }
}

搜索 DTO:

@Getter
@Setter
public class BusinessCustomersSearchParams {

    private String title;

    private List<BusinessCustomersStatus> status;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

搜索规范:

public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable)
    {
        Specification<BusinessCustomers> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (params.getTitle() != null) {
                predicates.add(cb.like(cb.lower(root.get("title")), "%" + params.getTitle().toLowerCase() + "%"));
            }

            Optional<BusinessCustomersStatus> optStatus = EnumSet.allOf(BusinessCustomersStatus.class)
                    .stream()
                    .filter(e -> e.name().equals(params.getStatus()))
                    .findAny();

            if(optStatus.isPresent()){
                final List<BusinessCustomersStatus> statuses = params.getStatus();
                if (statuses != null && !statuses.isEmpty()){
                    predicates.add(root.get("status").in(statuses));
                }
            }
            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };
        return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
    }

实体:

@Entity
@Table(name = "business_customers")
public class BusinessCustomers implements Serializable {
   
    ..........
    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 20)
    private BusinessCustomersStatus status;
    ......
}

但是我得到这个错误:

java.lang.IllegalArgumentException: No enum constant org.service.businesscustomers.BusinessCustomersStatus.4d29e059cf] with root cause
java.lang.IllegalArgumentException: No enum constant org.service.businesscustomers.BusinessCustomersStatus.4d29e059cf
        at java.base/java.lang.Enum.valueOf(Enum.java:273)

你知道我该如何解决这个问题吗?

POC 示例:https://github.com/rcbandit111/Search_specification_POC

我认为您混合了我对您 original question 的回答中公开的几个选项。

假设您将 status 定义为 List,如您提供的示例所示:

@Getter
@Setter
public class BusinessCustomersSearchParams {

    private String title;

    private List<BusinessCustomersStatus> status;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

您可以尝试在 Specification 中使用 in 子句:

public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable) {
    Specification<BusinessCustomers> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getTitle() != null) {
            predicates.add(cb.like(cb.lower(root.get("title")), "%" + params.getTitle().toLowerCase() + "%"));
        }
  
        // According to your comments, please, see this example
        // about how to wrap the List returned from your form
        final List<BusinessCustomersStatus> statuses = Optional.ofNullable(params.getStatus()).orElse(Collections.emptyList());
        if (statuses != null && !statuses.isEmpty()){
            predicates.add(root.get("status").in(statuses));
        }
        
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}

或者您可以遍历 status 集合以验证每个值并构建具有所需过滤条件的 or 谓词:

public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable) {
    Specification<BusinessCustomers> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getTitle() != null) {
            predicates.add(cb.like(cb.lower(root.get("title")), "%" + params.getTitle().toLowerCase() + "%"));
        }

        final List<BusinessCustomersStatus> statuses = params.getStatus();
        if (statuses != null && !statuses.isEmpty()){
            List<Predicate> statusPredicates = new ArrayList<Predicate>();

            EnumSet<BusinessCustomersStatus> businessCustomersStatusEnumSet = EnumSet.allOf(BusinessCustomersStatus.class);

            statuses.forEach(status -> {
                Optional<BusinessCustomersStatus> optStatus = businessCustomersStatusEnumSet
                    .stream()
                    .filter(e -> e.equals(status))
                    .findAny();

                if(optStatus.isPresent()){
                    statusPredicates.add(cb.equal(root.get("status"), cb.literal(status)));
                }    
            });
            
            predicates.add(
                cb.or(statusPredicates.toArray(new Predicate[statusPredicates.size()]))
            );
        }

        
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}

事实上,由于您已经在使用枚举而不是 Strings 我认为前面代码中出现的 forEach 循环可以简化如下:

public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable) {
    Specification<BusinessCustomers> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getTitle() != null) {
            predicates.add(cb.like(cb.lower(root.get("title")), "%" + params.getTitle().toLowerCase() + "%"));
        }

        final List<BusinessCustomersStatus> statuses = params.getStatus();
        if (statuses != null && !statuses.isEmpty()){
            List<Predicate> statusPredicates = new ArrayList<Predicate>();

            EnumSet<BusinessCustomersStatus> businessCustomersStatusEnumSet = EnumSet.allOf(BusinessCustomersStatus.class);

            statuses.forEach(status -> {
              if(businessCustomersStatusEnumSet.contains(status)){
                statusPredicates.add(cb.equal(root.get("status"), cb.literal(status)));
              }    
            });
            
            predicates.add(
                cb.or(statusPredicates.toArray(new Predicate[statusPredicates.size()]))
            );
        }

        
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}

甚至,采用不同的方法:

public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable) {
    Specification<BusinessCustomers> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getTitle() != null) {
            predicates.add(cb.like(cb.lower(root.get("title")), "%" + params.getTitle().toLowerCase() + "%"));
        }

        final List<BusinessCustomersStatus> statuses = params.getStatus();
        if (statuses != null && !statuses.isEmpty()){
            EnumSet<BusinessCustomersStatus> businessCustomersStatusEnumSet = EnumSet.allOf(BusinessCustomersStatus.class);

            List<Predicate> statusPredicates = businessCustomersStatusEnumSet.stream()
              .filter(status -> statuses.contains(status))
              .map(status -> cb.equal(root.get("status"), cb.literal(status)))
              .collect(Collectors.toList())
            ;
            
            if (statusPredicates != null && !statusPredicates.isEmpty()) {
              predicates.add(
                cb.or(statusPredicates.toArray(new Predicate[statusPredicates.size()]))
              );
            }
        }

        
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}

提供的第一种方法应该在典型用例中使用,虽然最后一种可能可以通过删除不正确的值来解决问题,但实际问题仍然存在:为什么您收到不正确的值枚举值排在第一位?请检查您的数据库记录以寻找不正确的记录,并确保 - 抱歉这么说,但有时它会发生,我面对自己 - 你在任何地方都使用注释值,例如 A,而不是关联的值有了它,active,继续这个例子;它们是应该存储在数据库中的值,由您的前端提交的值,等等。


如果您需要或更喜欢使用与您的枚举关联的 status 文字,您可以遵循以下方法。

首先,修改你的枚举并在转换过程中提供一个辅助方法:

public enum BusinessCustomersStatus {
    ACTIVE("active"),
    ONBOARDING("onboarding"),
    NOT_VERIFIED("not_verified"),
    VERIFIED("verified"),
    SUSPENDED("suspended"),
    INACTIVE("inactive");

    private String status;

    BusinessCustomersStatus(String status)
    {
        this.status = status;
    }

    public static BusinessCustomersStatus fromStatus(String status) {
        switch (status) {
            case "active": {
                return ACTIVE;
            }

            case "onboarding": {
                return ONBOARDING;
            }

            case "not_verified": {
                return NOT_VERIFIED;
            }

            case "verified": {
                return VERIFIED;
            }

            case "suspended": {
                return SUSPENDED;
            }

            case "inactive": {
                return INACTIVE;
            }

            default: {
                throw new UnsupportedOperationException(
                    String.format("Unkhown status: '%s'", status)
                );
            }
        }
    }
}

然后,修改 BusinessCustomersSearchParams 以使用 String 而不是 status 字段的枚举:

@Getter
@Setter
public class BusinessCustomersSearchParams {

    private String title;

    private List<String> status;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

最后,修改您的规范以处理转换过程。例如:

@Override
public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable)
{
    Specification<BusinessCustomers> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getTitle() != null) {
            predicates.add(cb.like(cb.lower(root.get("description")), "%" + params.getTitle().toLowerCase() + "%"));
        }

        final List<String> statuses = Optional.ofNullable(params.getStatus()).orElse(Collections.emptyList());
        if (statuses != null && !statuses.isEmpty()){
            List<BusinessCustomersStatus> statusesAsEnum = statuses.stream()
                .map(status -> BusinessCustomersStatus.fromStatus(status))
                .collect(Collectors.toList())
                ;

            predicates.add(root.get("status").in(statusesAsEnum));
        }

        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}

在插入过程中也可以使用fromStatus方法

话虽如此,我不知道它是否是您所需要的,但我认为您可以在这里使用 AttributeConverter。考虑例如:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter
public class BusinessCustomersStatusAttributeConverter
    implements AttributeConverter<BusinessCustomersStatus, String> {

  public String convertToDatabaseColumn( BusinessCustomersStatus value ) {
    if ( value == null ) {
      return null;
    }

    return value.getStatus();
  }

  public BusinessCustomersStatus convertToEntityAttribute( String value ) {
    if ( value == null ) {
      return null;
    }

    return BusinessCustomersStatus.fromStatus( value );
  }

}

您需要在枚举中提供额外的 getStatus 方法:

package org.merchant.database.service.businesscustomers;

public enum BusinessCustomersStatus {
    ACTIVE("active"),
    ONBOARDING("onboarding"),
    NOT_VERIFIED("not_verified"),
    VERIFIED("verified"),
    SUSPENDED("suspended"),
    INACTIVE("inactive");

    private String status;

    BusinessCustomersStatus(String status)
    {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    public static BusinessCustomersStatus fromStatus(String status) {
        switch (status) {
            case "active": {
                return ACTIVE;
            }

            case "onboarding": {
                return ONBOARDING;
            }

            case "not_verified": {
                return NOT_VERIFIED;
            }

            case "verified": {
                return VERIFIED;
            }

            case "suspended": {
                return SUSPENDED;
            }

            case "inactive": {
                return INACTIVE;
            }

            default: {
                throw new UnsupportedOperationException(
                    String.format("Unkhown status: '%s'", status)
                );
            }
        }
    }
}

现在,您的 BusinessCustomers 实体应修改为:

@Entity
@Table(name = "business_customers")
public class BusinessCustomers implements Serializable {
   
    ..........
    @Convert( converter = BusinessCustomersStatusAttributeConverter.class )
    private BusinessCustomersStatus status;
    ......
}

请注意,如果您在数据库中实施 AttributeConverter 方法,存储的值也将是与枚举关联的文字 status,而不是枚举 name本身。如果您更喜欢存储枚举值,请在 BusinessCustomers 中保留 @Enumerated 方法。如果您使用 AttributeConverter 方法进行测试,请记住将 status 列的值更新为与枚举对应的值。

最后,关于您最后的评论,您收到了 "status": "ACTIVE":这是因为您的 Mapstruct Mapper 提供了枚举的 name 作为 status BusinessCustomersFullDTO 中的字段。要处理适当的转换,请尝试以下操作:

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.merchant.config.BaseMapperConfig;
import org.merchant.database.entity.BusinessCustomers;
import org.merchant.database.service.businesscustomers.BusinessCustomersStatus;
import org.merchant.dto.businesscustomers.BusinessCustomersFullDTO;

@Mapper(config = BaseMapperConfig.class)
public interface BusinessCustomersMapper {

    @Mapping(source = "status", target = "status", qualifiedByName = "businessCustomersToDTOStatus")
    BusinessCustomersFullDTO toFullDTO(BusinessCustomers businessCustomers);


    @Named("busineessCustomersToDTOStatus")
    public static String businessCustomersToDTOStatus(final BusinessCustomersStatus status) {
        if (status == null) {
            return null;
        }

        return status.getStatus();
    }
}

在代码中,我们使用 @NamedqualifiedByName 来提供自定义转换。请参阅 relevant documentation.

你应该用常量值定义 enum,那么这些值就不会是 instanceof 的东西(也许由于其他原因它不喜欢枚举,但它仍然读取 no enum constant).

public static final int STATUS_ACTIVE       = 0;
public static final int STATUS_ONBOARDING   = 1;
public static final int STATUS_NOT_VERIFIED = 2;
public static final int STATUS_VERIFIED     = 3;
public static final int STATUS_SUSPENDED    = 4;
public static final int STATUS_INACTIVE     = 5;

public enum BusinessCustomersStatus {
   STATUS_ACTIVE,
   STATUS_ONBOARDING,
   STATUS_NOT_VERIFIED,
   STATUS_VERIFIED,
   STATUS_SUSPENDED,
   STATUS_INACTIVE
}

然后 @Enumerated 应该是 EnumType.ORDINAL:

@Enumerated(EnumType.ORDINAL)
@Column(name = "status")