仅当作为参数传递的列表具有元素时才考虑 JPA 'where in'

m3t*_*man 5 java spring hibernate jpa spring-data-jpa

我有一个查询应该根据各种参数进行过滤;这些参数之一是列表。如果列表中有条目,则应该根据条目进行过滤;但如果列表为空/空,则不应对该字段进行任何过滤。

我的想法是这样的:

@Query("select a from Alert a where a.date >= :startDate " +
            "and (((:countryIds) is null) or a.countryId in (:countryIds)) " +
            "and (((:typeIds) is null) or a.siteTypeId in (:typeIds)) ")
List<Alert> findBy(@Param("startDate") Date startDate,
                   @Param("countryIds") Set<Long> countryIds,
                   @Param("typeIds") Set<Long> typeIds);
Run Code Online (Sandbox Code Playgroud)

发送 null List 会抛出 NPE;发送一个空列表会生成以下 SQL,这是无效的

where alert0_.date >= '2018-01-01' and                                                       
 ((1, 123) is null or alert0_.countryId in (1, 123))
Run Code Online (Sandbox Code Playgroud)

我也尝试过在 JPQL 中使用and (((:countryIds) is empty) or a.countryId in (:countryIds)),但在尝试编译 JPQL(在应用程序启动时)时它也不起作用:Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: ??? is not mapped at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:171) 或使用 SpEL: "and (:#{countryIds.size() > 0} or (a.countryId in (:countryIds))) " 但同样,它不会编译 JPQL。

我想到的唯一解决方案是动态生成 JPQL,这很丑陋,或者填充所有现有值,countryIdssiteTypeIds效率很低。

JPA实现是Hibernate,数据库是MySQL。

m3t*_*man 12

经过大量的试验和错误,我找到了一个可以接受的 SpEL 工作解决方案;认为有些人可能会觉得它有用:

@Query("select a from Alert a where a.date >= :startDate " 
        "and (:#{#countryIds == null} = true or (a.countryId in (:countryIds))) " +
        "and (:#{#siteTypeIds == null} = true or (a.siteTypeId in (:siteTypeIds))) ")
List<Alert> findBy(@Param("startDate") Date startDate, 
                   @Param("countryIds") Set<Long> countryIds,
                   @Param("siteTypeIds") Set<Long> siteTypeIds);
Run Code Online (Sandbox Code Playgroud)

作为参数发送的集合必须null代替空集合。它产生可接受的 SQL:

select alert0_.alertId              as alertId1_0_, [...]
from alert alert0_
where alert0_.date >= '2018-01-01' and
      (0 = 1 or alert0_.countryId in (1, 123)) and
      (1 = 1 or alert0_.siteTypeId in (null));
Run Code Online (Sandbox Code Playgroud)