my2iu / Jinq

LINQ-style queries for Java 8
Other
660 stars 71 forks source link

WHERE clause not working well with ENUM types #56

Open kasimmm opened 7 years ago

kasimmm commented 7 years ago

Hi, I have a model with an enum property like this:

public class AuthKey {
private static final long serialVersionUID = 1L;

    public enum Status {
        PENDING,
        AUTHORIZED
    }

        @Enumerated(EnumType.STRING)
    private Status status;
        ......
}

The enum field is persisted in MySQL as varchar.

When I do a search like streamAll(em, AuthKey.class).where(k->k.getStatus() == Status.PENDING) it works just fine.

But when I do a search using a parameter

Status s = Status.PENDING;
streamAll(em, AuthKey.class).where(k->k.getStatus() == s)

it complains that 'Parameter value did not match expected type'

Any idea why? Thanks

my2iu commented 7 years ago

This seems to be a Hibernate error. Usually enums passed through Jinq work fine with Hibernate, so I'm not sure what the issue might be. Do you have more detailed error information from Hibernate? What did Hibernate say was the expected type and what did it think it received?

kasimmm commented 7 years ago

It said something like this

java.lang.IllegalArgumentException: Parameter value [PENDING] did not match expected type [com.abc.model.AuthKey$Status (n/a)]
        at org.jinq.jpa.JPAQueryComposer.fillQueryParameters(JPAQueryComposer.java:127)
    at org.jinq.jpa.JPAQueryComposer.executeAndReturnResultIterator(JPAQueryComposer.java:167)
    at org.jinq.orm.stream.QueryJinqStream.createWrappedStream(QueryJinqStream.java:37)
    at org.jinq.orm.stream.LazyWrappedStream.realizeStream(LazyWrappedStream.java:41)
    at org.jinq.orm.stream.LazyWrappedStream.collect(LazyWrappedStream.java:227)
    at org.jinq.orm.stream.NonQueryJinqStream.toList(NonQueryJinqStream.java:492)

I checked in the lambda expression, both the k.getStatus() and s are with Enum type.

I also tried fetching a record without the where clause then compared it with the parameter like result(0).getStatus() == s and it actually returned true.

my2iu commented 7 years ago

That seems strange. What happens when you move the Status enum to be its own top-level class instead of an inner-class? That's the only difference that I can see from my own test cases.

my2iu commented 7 years ago

Maybe making the Status enum static would work too?

kasimmm commented 7 years ago

Hmmmm still no luck with making it the top-level enum class. Ahhh just realised I forgot to mention that the parameter was actually passed from another service like this:

authKeyService.getAuthKey(Status.PENDING);

then in this AuthKeyService:

public AuthKey getAuthKey(Provider provider) throws AuthKeyServiceException {
        List<AuthKey> authKeys = streamAll(em, AuthKey.class).where(k -> k.getProvider() == provider).toList();
               ........
}

Maybe it is the reason?

my2iu commented 7 years ago

That should be fine. But there might be a classloader issue. Have you looked at this Stack Overflow about Hibernate and enums?

http://stackoverflow.com/questions/27622045/parameter-value-did-not-match-expected-type

my2iu commented 7 years ago

If that still doesn't work, you could try just dropping down to raw JPA/Hibernate there and seeing if the problem still comes up.

em.createQuery("SELECT * FROM AuthKey a WHERE a.status = :param")
   .setParameter("param", s)
   .getResultList()

It's seems like a strange Hibernate issue, so I'm just suggesting random ideas. What happens if you move the @Enumerated(EnumType.STRING) annotation onto the getter instead of the field? What happens if you make the status field public instead of private?

aVolpe commented 7 years ago

@my2iu all the enums are static!, even if they are nested.

I am using code like the one you provided @kasimmm with hibernate 5.2 with no issue.

kasimmm commented 7 years ago

@my2iu

@aVolpe I'm having some trouble switching to 5.2 (we are using 4.3) at the moment. Will definitely give it a try later.

Anyway, thank you both for your time and help :)

my2iu commented 7 years ago

If the raw JPA seems to work, then I don't know what the issue could be because that should be exactly the same code that Jinq is running.

The Stack Overflow solution is actually at the end of the page (it's not the first one listed). The accepted solution says there might be a classloader problem because of the way things are packaged up (it's possible to have two classes with the same name which Java thinks are different because they were loaded in different class loaders). But if the raw JPA worked for you, then that shouldn't be an issue.

I don't think I'll be able to help you further with this without some sample code that exhibits the problem that I can run and debug.

kasimmm commented 7 years ago

@my2iu Sorry for the late reply. I tried to replicate it with some sample code using same libs (hibernate-4.3.11, hibernate-jpa-2.0.1, etc.) but it worked just fine. The only difference is that our application runs as a spring boot application, which shouldn't really matter in this case. I'm using the raw JPA as a solution for now and will try to setup another env (spring boot/mysql) if I have some time.

@aVolpe Turns out that hibernate version isn't the issue as I was using the same version with some sample code and it worked fine.

Cheers.

OknoLombarda commented 3 years ago

I don't know if this was the case for kasimmm, but I have encountered similar issue. The problem was that we had spring devtools in dependencies of our project, so what it does is it loads all third-party libraries with default ClassLoader, but all project classes with a separate ClassLoader. Removing devtools from dependencies solved the issue

bodiam commented 2 years ago

@OknoLombarda you saved me. I spent 3+ hours debugging this, and your comment fixed it!