检查 Spring 数据 JPA 查询中的 List 参数是不是为空

Posted

技术标签:

【中文标题】检查 Spring 数据 JPA 查询中的 List 参数是不是为空【英文标题】:Check that a List parameter is null in a Spring data JPA query检查 Spring 数据 JPA 查询中的 List 参数是否为空 【发布时间】:2019-05-30 07:33:18 【问题描述】:

我有一个 Spring Boot 应用程序并使用 Spring Data JPA 来查询 MySQL 数据库。

我需要得到一个用一些参数过滤的课程列表。

我通常使用语法param IS NULL or (/*do something with param*/),这样如果参数为空,它就会忽略它。

使用简单的数据类型我没有问题,但是当涉及到对象列表时,我不知道如何检查 NULL 值。如何检查以下查询中的?3 参数是否为NULL

@Query("SELECT DISTINCT c FROM Course c\n" +
       "WHERE c.courseDate < CURRENT_TIME\n" +
       "AND (?1 IS NULL OR (c.id NOT IN (3, 4, 5)))\n" +
       "AND (?2 IS NULL OR (c.title LIKE ?2 OR c.description LIKE ?2))\n" +
       "AND ((?3) IS NULL OR (c.category IN ?3)) ")
List<Course> getCoursesFiltered(Long courseId, String filter, List<Category> categories);

错误是:

无法提取结果集; SQL [不适用];嵌套异常是 org.hibernate.exception.DataException: could not extract ResultSet[SQL: 1241, 21000]

在堆栈跟踪中我可以看到:

原因:java.sql.SQLException:操作数应包含 1 列

如果我的列表包含 3 个元素,则确实生成的查询将是 ... AND ((?3, ?4, ?5) IS NULL OR (c.category IN (?3, ?4, ?5)))。但是IS NULL 不能应用于多个元素(如果我的列表只包含一个元素,则查询可以正常工作)。

我尝试了size(?3) &lt; 1length(?3) &lt; 1(?3) IS EMPTY 等,但仍然没有运气。

【问题讨论】:

您需要多少细粒度的查询控制?如果您使用的是 Spring Boot,您是否考虑过使用基于方法名称自动生成的 CRUD 方法?这样,您可以在参数上使用 @Nullable 注释,它可能开箱即用。见docs.spring.io/spring-data/jpa/docs/current/reference/html/… 否则,我相信在 mysql 中,说“X 在 (NULL) 中的位置”是有效的语法,所以我认为您上面的查询应该有效。您的问题似乎在于提取 ResultSet,而不是运行查询。您能否为您的课程类添加完整的代码以及实体映射和您遇到的错误的完整堆栈跟踪? (当然,虽然这可能不是你想要的答案,但最简单的解决方案是有两种单独的查询方法,一种检查类别,另一种不检查,还有一个委托在 Java 代码中检查您的类别列表并根据它是否为空来调用适当的查询的方法,即如果它为空,则根本不将其包含在查询中) 感谢马特,我已经使用基于方法名称的查询来满足其他需求,但实际上我在这里谈到的查询已经针对示例进行了简化,不能那样做。顺便说一下where X in (NULL) 按预期工作,是(?3) IS NULL 语句导致了问题。我的Course 类和实体映射非常基础,没什么特别的。我确实从管理器类中调用了存储库方法,我可以按照你的建议在那里做一些技巧,但我很确定有一种更清洁的方法来做到这一点:) 【参考方案1】:

Spring 使我们能够使用对象模型作为带有@Param 注释的查询参数。在动态查询中检查列表类型参数是否为空的另一种解决方法是在我们的搜索模型上放置一个 getter 方法。

假设您有一个名为 PersonSearchFilter 的搜索过滤器模型,您希望通过该模型搜索 Person 模型;

@Query(value = "SELECT a FROM Person a WHERE 1=1 "
            + "and (:##searchFilter.name is null or a.name like %:##searchFilter.name%) "
            + "and (:##searchFilter.statusListSize = 0 or a.status in (:##searchFilter.statusList)) ")
Optional<List<Person>> findByFilter(@Param("searchFilter") PersonSearchFilter searchFilter);


public class PersonSearchFilter 

    private String name;
    private List<String> statusList;
    
    public String getName() 
        return name;
    

    public List<String> getStatusList() 
        return statusList;
    

    public int getStatusListSize() 
        return CollectionUtils.isEmpty(statusList) ? 0:statusList.size();
    


【讨论】:

你试过:##searchFilter.statusList.size而不是:##searchFilter.statusListSize吗?它应该适用于 SpEL。【参考方案2】:

好吧,我在半夜醒来后想到了一件事:

@Query("SELECT DISTINCT c FROM Course c\n" +
       "WHERE c.courseDate < CURRENT_TIME\n" +
       "AND (?1 IS NULL OR (c.id NOT IN (3, 4, 5)))\n" +
       "AND (?2 IS NULL OR (c.title LIKE ?2 OR c.description LIKE ?2))\n" +
       "AND (COALESCE(?3) IS NULL OR (c.category IN ?3)) ")
List<Course> getCoursesFiltered(Long courseId, String filter, List<Category> categories);

解决方案是在IS NULL 之外使用COALESCE,这样它就可以处理多个值。这样,如果列表至少包含一个非空值,则第二个表达式 ((c.category IN ?3)) 将完成过滤工作。

下次我至少要等一整晚才问问题:)

【讨论】:

这似乎是一个 hack。 Hibernate应该给出官方的解决方案。 @CDT:看来至少Spring团队正在调查这个问题:见DATAJPA-209。 对我来说也是唯一有效的解决方案。试过 with 是空的,是 null ,没有成功。 此解决方案适用于 MySql,但不适用于 SQL Server。 在 PostgreSQL 上运行良好【参考方案3】:

这并没有严格回答您的问题,但一个简单的解决方案是在您的存储库中使用另一种方法,该方法在调用查询之前检查您的列表并将它们默认为具有虚拟值的列表。比如:

@Query("SELECT DISTINCT c FROM Course c\n" +
       "WHERE c.courseDate < CURRENT_TIME\n" +
       "AND (?1 IS NULL OR (c.id NOT IN (3, 4, 5)))\n" +
       "AND (?2 IS NULL OR (c.title LIKE ?2 OR c.description LIKE ?2))\n" +
       "AND ('DUMMYVALUE' IN ?3 OR (c.category IN ?3)) ")
// make this private to keep it safe
private List<Course> getCoursesFiltered(Long courseId, String filter, List<Category> categories);

// public helper method to put a dummy value in the list that you can check for
public List<Course> getNullableCoursesFiltered(Long courseId, String filter, List<Category> categories) 
    if(categories == null) 
        categories = Arrays.asList("DUMMYVALUE");
    
    return getCoursesFiltered(courseId, filter, categories);

【讨论】:

如何在存储库中定义方法?你不是在处理接口吗? @geneb。 Java 8 引入了将具体方法放入接口的能力

以上是关于检查 Spring 数据 JPA 查询中的 List 参数是不是为空的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Spring Data JPA 了解底层数据库名称

如何将spring数据jpa规范查询中的distinct和sort与join结合起来

spring-data-jpa中的查询方法

使用 spring boot JPA hibernate 存储库检查用户名和密码的 Mysql 查询不起作用。我该怎么办?

如何在JAVA JPA Spring Boot中的一个SQL查询中选择多个数据

在 Spring JPA 中跨两个不同的数据库表进行查询