JPA2: Case-insensitive like matching anywhere JPA2: Case-insensitive like matching anywhere java java

JPA2: Case-insensitive like matching anywhere


It may seem a little awkward at first, but it is type-safe. Building queries from strings isn't, so you notice errors at runtime instead of at compile time. You can make the queries more readable by using indentations or taking each step separately, instead of writing an entire WHERE clause in a single line.

To make your query case-insensitive, convert both your keyword and the compared field to lower case:

query.where(    builder.or(        builder.like(            builder.lower(                root.get(                    type.getDeclaredSingularAttribute("username", String.class)                )            ), "%" + keyword.toLowerCase() + "%"        ),         builder.like(            builder.lower(                root.get(                    type.getDeclaredSingularAttribute("firstname", String.class)                )            ), "%" + keyword.toLowerCase() + "%"        ),         builder.like(            builder.lower(                root.get(                    type.getDeclaredSingularAttribute("lastname", String.class)                )            ), "%" + keyword.toLowerCase() + "%"        )    ));


As I commented in the (currently) accepted answer, there is a pitfall using on one hand DBMS' lower() function and on the other hand java's String.toLowerCase() as both method are not warrantied to provide the same output for the same input string.

I finally found a much safer (yet not bullet-proof) solution which is to let the DBMS do all the lowering using a literal expression:

builder.lower(builder.literal("%" + keyword + "%")

So the complete solution would look like :

query.where(    builder.or(        builder.like(            builder.lower(                root.get(                    type.getDeclaredSingularAttribute("username", String.class)                )            ), builder.lower(builder.literal("%" + keyword + "%")        ),         builder.like(            builder.lower(                root.get(                    type.getDeclaredSingularAttribute("firstname", String.class)                )            ), builder.lower(builder.literal("%" + keyword + "%")        ),         builder.like(            builder.lower(                root.get(                    type.getDeclaredSingularAttribute("lastname", String.class)                )            ), builder.lower(builder.literal("%" + keyword + "%")        )    ));

Edit:
As @cavpollo requested me to give example, I had to think twice about my solution and realized it's not that much safer than the accepted answer:

DB value* | keyword | accepted answer | my answer------------------------------------------------elie     | ELIE    | match           | matchÉlie     | Élie    | no match        | matchÉlie     | élie    | no match        | no matchélie     | Élie    | match           | no match

Still, I prefer my solution as it does not compare the outcome out two different functions that are supposed to work alike. I apply the very same function to all character arrays so that comparing the output become more "stable".

A bullet-proof solution would involve locale so that SQL's lower() become able to correctly lower accented characters. (But this goes beyond my humble knowledge)

*Db value with PostgreSQL 9.5.1 with 'C' locale


This work for me :

CriteriaBuilder critBuilder = em.getCriteriaBuilder();CriteriaQuery<CtfLibrary> critQ = critBuilder.createQuery(Users.class);Root<CtfLibrary> root = critQ.from(Users.class);Expression<String> path = root.get("lastName");Expression<String> upper =critBuilder.upper(path);Predicate ctfPredicate = critBuilder.like(upper,"%stringToFind%");critQ.where(critBuilder.and(ctfPredicate));em.createQuery(critQ.select(root)).getResultList();