Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring CriteriaBuilder search enum by it's name

When I'm trying to search enum by his name with Specification in my DB using Spring @Repository, I'm getting the following exception:

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

But in the DB the enum saved as VARCHAR(255) so why I can search the enum with String, why It's need to by a Enum type?

DTO class

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

DataBase connector

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

Starter

@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")
        );
    }
}

I would appreciate for an explanation.

like image 954
Daniel Taub Avatar asked May 07 '18 13:05

Daniel Taub


1 Answers

You're trying to compare Enum and String.

Try this way:

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

I will try to provide some explanations why this is happening. Hibernate fetches ResultSet from Database to Class signature using Reflection.

Observing stacktrace you will see something like:

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) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]

Hibernate performs a bunch of validations before setting a parameter.

Here is the last method which initializes a root cause for 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 )
                        )
                );
            }
        }
    }

The method private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType) which has a bunch of checks retuns false because your expected type is class com.whatever.Hello and value to check is HELLO what is String, but Enum type and String are incompatible!

If you will put proper Enum in your search criteria, validation will pass because private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType) contains isInstance check which will pass:

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

After all the checks Hibernate extracts values from ResultSet and builds the List, in this particular case, elements of List are fetched using reflection.

like image 188
J-Alex Avatar answered Nov 19 '22 08:11

J-Alex