Spring CriteriaBuilder用它的名字搜索枚举

时间:2018-05-07 13:50:52

标签: java spring hibernate jpa enums

当我尝试使用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")
        );
    }
}

我很感激您的解释。

2 个答案:

答案 0 :(得分:1)

您正在尝试比较EnumString

尝试这种方式:

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

我会尽力解释为什么会这样。 Hibernate使用ResultSet从数据库中将Class提取到Reflection签名。

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

  

org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54)   〜[hibernate-core-5.2.16.Final.jar:5.2.16.Final] at   org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27)   〜[hibernate-core-5.2.16.Final.jar:5.2.16.Final] at   org.hibernate.query.internal.QueryParameterBindingImpl.validate(QueryParameterBindingImpl.java:90)   〜[hibernate-core-5.2.16.Final.jar:5.2.16.Final] at   org.hibernate.query.internal.QueryParameterBindingImpl.setBindValue(QueryParameterBindingImpl.java:55)   〜[hibernate-core-5.2.16.Final.jar:5.2.16.Final] at   org.hibernate.query.internal.AbstractProducedQuery.setParameter(AbstractProducedQuery.java:486)   〜[hibernate-core-5.2.16.Final.jar:5.2.16.Final] at   org.hibernate.query.internal.AbstractProducedQuery.setParameter(AbstractProducedQuery.java:104)   〜[冬眠核-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的元素。

答案 1 :(得分:-1)

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"

是String的实例。

这里没有自动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")
    );