JPA Criteria: Invalid SQL with IN predicate
JPA Criteria is awesome when you need to compose complex queries from multiple blocks, but it can cause a lot of troubles when the underlying SQL code fails to check some obvious scenarios.
The concept
When dealing with Java List
s, we can almost always assume (if written correctly) that code will work as expected with empty lists if we respect the contract (don’t pass null
as a parameter!).
Imagine the following code:
We have:
- A static list of persons;
- A method that searches the list of persons when provided with a list of ids.
This is the console output:
[Person [id=1], Person [id=2]]
[]
Nothing special, right? If we pass an empty list of ids, the method won’t have anything to search for!
The problem
Now, lets imagine the same kind of situation, but working with:
- JPA 2.1 with Criteria;
- Hibernate as JPA provider;
- Postgres as database.
The following query should search for the Person
entity that matches a list of ids (but we are bad boys and provided an empty List
):
The call to typedQuery.getResultList()
should return an empty resultset. The generated SQL is (attention to the where clause):
And the execution causes:
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
...
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near ")"
Posição: 127
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2477)
...
Indeed, the generated SQL is invalid. One of the approaches (and the most performatic one) would be to check for empty List
s all the time, but we may end up with a lot of boilerplate code.
Solution(s)
We can solve this problem with creative ways, and I came up with two, depending on the situation.
#1 - Create your own IN method or utility
This one is easy: instead of using Expression#in(Collection)
, we can create a method that will handle the empty collection case for us, returning a Predicate
:
What’s happenning here: If List.isEmpty() == true
, we return an condition that is always false. In this case: true=false
.
Here’s the relevant part using this code:
And here is the generated SQL (? is a placeholder for false
):
No more exceptions!
#2 - Decorator pattern
The solution #1 is great when you have something like a AbstractRepository
or AbstractDAO
that you can put the created reusable method. But when that’s not an option, we can come up with a Decorator
that will only be applied to the Path
or Expression
that we are checking for matches in the list.
First, a new class must be created:
Then we can decorate the entity Path
object and use the #in(Collection)
method that adheres to our contract:
No more exceptions!²
Conclusion
In the end, you should pick the option that suits better your project’s situation.
It would be nice to have the #1 solution in an AbstractRepository
where it can be reused in subclasses. But if this problem was discovered when that’s not an option, the #2 solution also solves the problem and is less intrusive to the class hierarchy.
Which one to choose is up to you!