Search code examples
javamysqlspring-bootjpajpql

Spring Boot JPA: Passing Multiple Values for the Same Parameter (JPQL)


I'm using JPQL to write SQL queries in JPA's CurdRepository interface. I was able to write more than one query with named parameters and they work like a charm.

However, my application requires that I match an unknown number of strings with more than one column in a table. To my knowledge, in SQL it'd look something like this:

SELECT * FROM TABLE WHERE COLUMN_1 LIKE "%string_1%" OR COLUMN_1 LIKE"%string_2%" ... OR COLUMN_2 LIKE "%string_1%" OR COLUMN_2 LIKE "%string_2%" ... ...

I tried this in MySQL and it works fine. I couldn't figure out how to depict this in JPQL, however. I was able to pass multiple values for the IN clause by setting the named parameter to a List, as follows:

 @Query("SELECT t FROM Table t WHERE TABLE_ID IN (:table_ids)")
public Page<Table> findByParameters(@Param("table_ids") List<Integer> table_ids, Pageable page)

I tried doing the same thing with the LIKE clause but I end up with 0 results if I pass more than 1 value. I face the same problem with the LOCATE clause as well.

Worst case scenario, I can set up one query that works on one String, and call it several times and then merge all the results. I'd very much rather avoid this however, as it'd be nothing more than a duct-tape solution.

I looked online for solutions for hours but haven't found anything that addresses my concern. Any tip is highly appreciated.

Thanks.


Solution

  • Because @Query needs to be fixed at compile time using JPQL or even native query will make this kind of things hard to implement especially in a type safe way.

    So I realize you are after JPQL solution but this is an awesome opportunity to learn and take advantage of Specification interface and JPA's CriteriaQuery. This is exactly it is for.

    Take a look at the following repository:

    public interface Table1Repository            // to use specifications in queries 
            extends JpaRepository<Table1, Long>, JpaSpecificationExecutor<Table1> {
    
        @SuppressWarnings("serial")
        public static Specification<Table1> multiLikeColumn1(List<String> likePatterns) {
            return new Specification<Table1>() {
                @Override
                public Predicate toPredicate(Root<Table1> root, CriteriaQuery<?> query,
                                                CriteriaBuilder criteriaBuilder) {
                    Path<String> column1 = root.get("column1");
                    // create a Predicate for each "column1 like 'xy%az%' you need
                    List<Predicate> predicates = likePatterns.stream()
                        .map(likePattern -> criteriaBuilder.like(column1, likePattern))
                        .collect(Collectors.toList());
                    // then "concatenate" list of likes with "OR"
                    return criteriaBuilder.or(predicates.toArray(new Predicate[]{}));
                }
            };
        }
    
    }
    

    It might look a bit complex but actually it is not when you get familiar with it. Usage is simple, like:

    @Resource
    private Table1Repository repo;
    repo.findAll(Table1Repository.multiLikeColumn1(Arrays.asList("%X%","%Z%")))