【问题标题】:Spring CriteriaBuilder search enum by it's nameSpring CriteriaBuilder 按名称搜索枚举
【发布时间】:2018-10-17 08:39:04
【问题描述】:

当我尝试使用 Specification 在我的数据库中使用 Spring @Repository 按他的名字搜索枚举时,我收到以下异常:

Caused by: java.lang.IllegalArgumentException: Parameter value [HELLO] did not match expected type [application.springEnum.Hello (n/a)]

但在数据库中,枚举保存为VARCHAR(255),那么为什么我可以使用String 搜索枚举,为什么需要使用枚举类型?

DTO 类

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DTO {
    @Id
    private String id;
    @Enumerated(EnumType.STRING)
    private Hello helloEnum; // My Enum
}

数据库连接器

@Repository
public interface Connector extends JpaRepository<DTO, String>, JpaSpecificationExecutor<DTO> {
}

初学者

@Component
public class Starter {
    @Autowired
    private Connector connector;

    @PostConstruct
    public void init(){
        // Create DTO entity
        DTO dto = DTO.builder()
                .id(UUID.randomUUID().toString())
                .helloEnum(Hello.HELLO)
                .build();
        // Save the entity in the db
        connector.save(dto);

        // Search by the name, here I get the excpetion
        List<DTO> result = connector.findAll((root, query, cb) ->
                cb.equal(root.get("helloEnum"), "HELLO")
        );
    }
}

我会很感激你的解释。

【问题讨论】:

    标签: java spring hibernate jpa enums


    【解决方案1】:

    您正在尝试比较 EnumString

    试试这个方法:

    List<DTO> result = connector.findAll((root, query, cb) ->
                    cb.equal(root.get("helloEnum"), Hello.HELLO);
    

    我将尝试提供一些解释为什么会发生这种情况。 Hibernate 使用Reflection 从数据库中获取ResultSetClass 签名。

    观察堆栈跟踪你会看到类似的东西:

    org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] 在 org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] 在 org.hibernate.query.internal.QueryParameterBindingImpl.validate(QueryParameterBindingImpl.java:90) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] 在 org.hibernate.query.internal.QueryParameterBindingImpl.setBindValue(QueryParameterBindingImpl.java:55) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] 在 org.hibernate.query.internal.AbstractProducedQuery.setParameter(AbstractProducedQuery.java:486) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] 在 org.hibernate.query.internal.AbstractProducedQuery.setParameter(AbstractProducedQuery.java:104) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]

    Hibernate 在设置参数之前会执行一系列验证。

    这是为Exception 初始化根本原因的最后一个方法:

    public <P> void validate(Type paramType, Object bind, TemporalType temporalType) {
            if ( bind == null || paramType == null ) {
                // nothing we can check
                return;
            }
            final Class parameterType = paramType.getReturnedClass();
            if ( parameterType == null ) {
                // nothing we can check
                return;
            }
    
            if ( Collection.class.isInstance( bind ) && !Collection.class.isAssignableFrom( parameterType ) ) {
                // we have a collection passed in where we are expecting a non-collection.
                //      NOTE : this can happen in Hibernate's notion of "parameter list" binding
                //      NOTE2 : the case of a collection value and an expected collection (if that can even happen)
                //          will fall through to the main check.
                validateCollectionValuedParameterBinding( parameterType, (Collection) bind, temporalType );
            }
            else if ( bind.getClass().isArray() ) {
                validateArrayValuedParameterBinding( parameterType, bind, temporalType );
            }
            else {
                if ( !isValidBindValue( parameterType, bind, temporalType ) ) {
                    throw new IllegalArgumentException(
                            String.format(
                                    "Parameter value [%s] did not match expected type [%s (%s)]",
                                    bind,
                                    parameterType.getName(),
                                    extractName( temporalType )
                            )
                    );
                }
            }
        }
    

    有一堆检查的方法private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType)返回false,因为您的预期类型是class com.whatever.Hello,要检查的值是HELLO什么是String,但Enum类型和String是不兼容!

    如果您在搜索条件中输入正确的Enum,验证将通过,因为private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType) 包含isInstance 检查将通过:

    else if ( expectedType.isInstance( value ) ) {
        return true;
    }
    

    在所有检查之后,Hibernate 从ResultSet 中提取值并构建List,在这种特殊情况下,List 的元素是使用反射获取的。

    【讨论】:

    • 我知道,但是在数据库中它保存为 varchar
    • @Daniel Taub 请查看更新后的答案。
    • 有什么方法可以使用标准构建器 Like 方法和枚举?
    【解决方案2】:
    public class Main {
    enum Hello {
        HELLO
    }
    public static void main(String[] args) {
        Hello hello = Hello.HELLO;
        System.out.println(hello.toString().equals("HELLO")); //true
        System.out.println("HELLO".equals(hello.toString())); //true
        System.out.println(hello.toString() == "HELLO"); //true
        System.out.println(hello.equals("HELLO")); //false
        System.out.println("HELLO".equals(hello)); //false
    //        System.out.println(hello == "HELLO"); //incompatible types
    }
    }
    

    不完全确定,但 equal() 参数必须是同一类型。

    root.get("helloEnum")
    

    是 Hello 的实例

    "HELLO"
    

    是字符串的实例。

    这里没有对 enum 进行自动转换的 toString()。 只是为了确保尝试:

    // Search by the name, here I get the excpetion
        List<DTO> result = connector.findAll((root, query, cb) ->
                cb.equal(root.get("helloEnum").toString(), "HELLO")
        );
    

    【讨论】:

    • 关于它在数据库中的表示方式,是ORM的东西。
    • 事件不编译
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    • 2016-12-07
    相关资源
    最近更新 更多