Spring Data和本地查询与分页

120

在一个web项目中,我正在使用最新的spring-data(1.10.2)与MySQL 5.6数据库,并尝试使用本地查询进行分页,但是在启动时遇到了org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException异常。

更新:20180306 此问题现在在Spring 2.0.4中已修复。对于那些仍然感兴趣或被困在旧版本中的人,请查看相关答案和评论以获取解决方法。

根据spring-data文档中的示例50-使用@Query,可以通过指定查询本身和countQuery实现这一点,如下所示:

public interface UserRepository extends JpaRepository<User, Long> {
  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

好奇的话,在NativeJpaQuery类中,我可以看到它包含以下代码来检查它是否是有效的jpa查询:

public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
   super(method, em, queryString, evaluationContextProvider, parser);
   JpaParameters parameters = method.getParameters();
   boolean hasPagingOrSortingParameter = parameters.hasPageableParameter() || parameters.hasSortParameter();
   boolean containsPageableOrSortInQueryExpression = queryString.contains("#pageable") || queryString.contains("#sort");
   if(hasPagingOrSortingParameter && !containsPageableOrSortInQueryExpression) {
       throw new InvalidJpaQueryMethodException("Cannot use native queries with dynamic sorting and/or pagination in method " + method);
   }
}

我的查询包含一个 Pageable 参数,因此 hasPagingOrSortingParametertrue,但它还在查询字符串中查找一个 #pageable#sort 序列,而我没有提供它。

我尝试在查询末尾添加 #pageable(它是一个注释),这样验证就会通过,但在执行时会失败,说查询需要一个额外的参数:3 而不是 2。

有趣的是,如果我在运行时手动将 containsPageableOrSortInQueryExpressionfalse 改为 true,则查询工作正常,因此我不知道为什么它要检查该字符串是否在我的 queryString 中,并且我不知道如何提供它。

任何帮助将不胜感激。

更新于 2018 年 01 月 30 日 看起来,spring-data 项目的开发人员正在使用 Jens Schauder 的PR 修复此问题。


不,我还没有找到解决方案。我在Spring JIRA上创建了一个工单,但是没有得到回应:https://jira.spring.io/browse/DATAJPA-928。最终,我并不需要分页,所以我没有进一步调查或者在那个工单上继续推动。 - Lasneyx
3
好的,谢谢。作为一种解决方法,你可以用"\n#pageable\n"代替"#pageable",但我希望这个问题在将来会得到修复。 - Janar
1
当前的修复对我来说使情况变得更糟。现在我的PageAble参数完全被忽略了,查询被无限执行。因此,如果您插入了那个\n#pageable\n解决方法,请确保将其删除,否则您会遇到麻烦。 - Rüdiger Schulz
对我来说,我使用了@Param("lastname")注解,问题出在WHERE LASTNAME = \":lastname\",这里的双引号是不必要的。因此,问题是我简单地忘记从SQL参数中删除双引号。 - lechat
正如其他人所指出的那样,在较新版本的JPA中不再需要添加#pageable,但请确保您的Pageable参数已指定了Sort,否则它将被忽略。 - undefined
20个回答

63

在此我提前道歉,这基本上总结了原始问题和Janar的评论,但是...

我遇到了同样的问题:我发现Spring Data 的示例50是我的解决方案,用于满足我需要使用分页的本地查询,但Spring在启动时抱怨我不能使用带有本地查询的分页。

我只想报告一下,我成功地运行了所需的本地查询,并使用以下代码进行了分页:

    @Query(value="SELECT a.* "
            + "FROM author a left outer join mappable_natural_person p on a.id = p.provenance_id "
            + "WHERE p.update_time is null OR (p.provenance_name='biblio_db' and a.update_time>p.update_time)"
            + "ORDER BY a.id \n#pageable\n", 
        /*countQuery="SELECT count(a.*) "
            + "FROM author a left outer join mappable_natural_person p on a.id = p.provenance_id "
            + "WHERE p.update_time is null OR (p.provenance_name='biblio_db' and a.update_time>p.update_time) \n#pageable\n",*/
        nativeQuery=true)
public List<Author> findAuthorsUpdatedAndNew(Pageable pageable);

在代码块中注释掉的countQuery是必需的,以便将Page<Author>用作查询的返回类型,“#pageable”注释周围的换行符是为了避免运行时错误,该错误涉及到预期参数的数量(解决问题的解决方法)。我希望这个错误能够很快得到修复...


1
在我的情况下, 使用?#{#pageable} 而不是 \n#pageable\n. - Dmitry Stolbov
11
这有效。至于参数,您仍然可以使用命名参数,例如: authorId。我将您的 \n#pageable\n 更改为适用于postgresql的 --#pageable\n。正如您建议的那样,需要countQuery,因为许多示例使用List<>而不是Page<>,您的答案非常正确。我查看了相同的文档,如果不是因为您的答案,我可能已经放弃了。 - Abhishek Dujari
4
如果您不编写countQuery并让框架处理,有时会出现无法正确编写查询计数的情况,您可能会看到一个无效的字段列表错误。您可以通过将以下条目添加到属性文件来始终分析SQL语句:logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE单独添加计数查询将解决该问题。这可能取决于您使用的框架版本和数据库。 - Nalaka
1
在我的情况下,只有 /*:pageable*/ 能够正常工作(使用 SQL Server、Hibernate 5.4.1.Final 和 Spring Boot 2.2.1)。 - marioosh
1
是我自己的问题还是其他人也注意到了,在Postgres中使用--,在大多数数据库引擎中使用/**/表示“请注释此代码块”?我是否误解了答案?如果我理解有误,请原谅。 - wmrodrigues

54

这是针对使用Spring Data JPA 2.0.4版本之前的程序的一个hack。

代码已经适用于PostgreSQL和MySQL:

public interface UserRepository extends JpaRepository<User, Long> {

@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1 ORDER BY ?#{#pageable}",
       countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
       nativeQuery = true)
   Page<User> findByLastname(String lastname, Pageable pageable);   
}

ORDER BY ?#{#pageable} 是用于 Pageable

countQuery 用于 Page<User>

1
我不知道。尝试以下三个步骤:1. 启用SQL语句记录 - 在application.properties中设置spring.jpa.show-sql=false; 2. 在你的nativeQuery中设置?#{#pageable}; 3. 分析日志中的结果SQL。@Lasneyx为Spring Data JPA的这种特殊情况创建了问题DATAJPA-928 - Dmitry Stolbov
你好 @Dmitry ,我拿到了 Pageable pageable,接下来应该怎么做才能实现分页呢?谢谢! - Because i hate myself

18

我在这里添加答案,仅作为那些使用更高版本Spring Boot的用户的占位符。在Spring Boot 2.4.3上,我观察到不需要任何解决方法,以下代码直接为我工作:

public interface UserRepository extends JpaRepository<User, Long> {
    @Query(value="SELECT * FROM USERS WHERE LASTNAME = ?1", nativeQuery=true)
    Page<User> findByLastname(String lastname, Pageable pageable);
}

countQuery 的定义是不必要的,实际上调用 Page#getTotalElements() 已经返回了正确的计数,这个计数由 JPA 的内部计数查询返回。

上述代码非常强大,提供了通过本地查询进行分页的功能,同时将结果返回到实际的 Java 实体中(而不是有时候必须使用的丑陋而庞大的 List<Object[]>)。


1
这对我有用,避免在选择查询中使用表别名,它不起作用! - Nisarg Patil
这对我在Spring Boot 3中也起作用了。这应该是答案。 - Because i hate myself
3
但是当我使用别名时,我必须添加countQuery,否则我会得到oracle.jdbc.OracleDatabaseException: ORA-00904: "myAlias" 无效标识符。 - hakima maarouf
1
如果查询具有复杂的连接,它是否有效?生成的实际查询是什么? - saran3h
在使用MSSQL时,我遇到了这个错误:TOP或FETCH子句的行数参数提供的行数必须是整数。 - Amir Keshavarz

14

仅供记录,使用H2作为测试数据库,在运行时使用MySQL,这种方法可行(例如:组中最新的对象):

@Query(value = "SELECT t.* FROM t LEFT JOIN t AS t_newer " +
        "ON t.object_id = t_newer.object_id AND t.id < t_newer.id AND o_newer.user_id IN (:user_ids) " +
        "WHERE t_newer.id IS NULL AND t.user_id IN (:user_ids) " +
        "ORDER BY t.id DESC \n-- #pageable\n",
        countQuery = "SELECT COUNT(1) FROM t WHERE t.user_id IN (:user_ids) GROUP BY t.object_id, t.user_id",
        nativeQuery = true)
Page<T> findByUserIdInGroupByObjectId(@Param("user_ids") Set<Integer> userIds, Pageable pageable);

Spring Data JPA 1.10.5,H2 1.4.194,MySQL Community Server 5.7.11-log(innodb_version 5.7.11)。

意思是使用 Spring Data JPA 1.10.5、H2 1.4.194 和 MySQL Community Server 5.7.11-log(innodb_version 5.7.11)进行 IT 技术开发。

曾在PostgreSQL工作。谢谢! - mchern1kov
如果我们需要动态地将升序或降序的排序方式传递给查询,该怎么办? - Kulbhushan Singh

9

试试这个:

public interface UserRepository extends JpaRepository<User, Long> {
  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1 ORDER BY /*#pageable*/",
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

("/* */"Oracle 符号)

,它用于注释代码。

由于某些原因,生成的查询在 pageable 后面有逗号,类似于这样 "ORDER BY /#pageable/," 导致语法错误。 - d-man
我移除了"order by",但是出现了异常字符:"#"。 - hicham abdedaime

8
我和@Lasneyx有完全相同的症状。我对于Postgres原生查询的解决方法是:
@Query(value = "select * from users where user_type in (:userTypes) and user_context='abc'--#pageable\n", nativeQuery = true)
List<User> getUsersByTypes(@Param("userTypes") List<String> userTypes, Pageable pageable);

在我使用DB2时也有效。 - Anders Metnik
@Query(nativeQuery = true, value= "select a.* from rqst a LEFT OUTER JOIN table_b b ON a.id= b.id where a.code='abc' ORDER BY --#pageable\n") 不起作用:无法确定参数$2的数据类型。 - Developer

6
我使用Oracle数据库时,没有得到结果,而是出现了由D-Man上面提到的生成逗号引起的错误。
我的解决方法如下:
Pageable pageable = new PageRequest(current, rowCount);

您可以看到,在创建可分页时,没有使用order by。

而在DAO中的方法为:

public interface UserRepository extends JpaRepository<User, Long> {
  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1 /*#pageable*/ ORDER BY LASTNAME",
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);
 }

5
我可以成功地在Spring Data JPA 2.1.6中实现分页,方法如下:
@Query(
 value =SELECT * FROM Users”, 
 countQuery =SELECT count(*) FROM Users”, 
 nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);

原生查询中的可分页参数在哪里? - java dev

2

以下两种方法在MySQL中用于分页本地查询都是有效的。但是在H2数据库中无法使用,会报SQL语法错误。

  • ORDER BY ?#{#pageable}
  • ORDER BY a.id \n#pageable\n

2

我使用Postgres数据库,并在Groovy中实现了以下方法,它对我很有帮助:

@RestResource(path="namespaceAndNameAndRawStateContainsMostRecentVersion", rel="namespaceAndNameAndRawStateContainsMostRecentVersion")
    @Query(nativeQuery=true,
            countQuery="""
            SELECT COUNT(1) 
            FROM 
            (
                SELECT
                ROW_NUMBER() OVER (
                    PARTITION BY name, provider_id, state
                    ORDER BY version DESC) version_partition,
                *
                FROM mydb.mytable
                WHERE
                (name ILIKE ('%' || :name || '%') OR (:name = '')) AND
                (namespace ILIKE ('%' || :namespace || '%') OR (:namespace = '')) AND
                (state = :state OR (:state = ''))
            ) t
            WHERE version_partition = 1
            """,
            value="""
            SELECT id, version, state, name, internal_name, namespace, provider_id, config, create_date, update_date 
            FROM 
            (
                SELECT 
                ROW_NUMBER() OVER (
                    PARTITION BY name, provider_id, state
                    ORDER BY version DESC) version_partition,
                *
                FROM mydb.mytable
                WHERE 
                (name ILIKE ('%' || :name || '%') OR (:name = '')) AND
                (namespace ILIKE ('%' || :namespace || '%') OR (:namespace = '')) AND
                (state = :state OR (:state = ''))       
            ) t            
            WHERE version_partition = 1             
            /*#{#pageable}*/
            """)
    public Page<Entity> findByNamespaceContainsAndNameContainsAndRawStateContainsMostRecentVersion(@Param("namespace")String namespace, @Param("name")String name, @Param("state")String state, Pageable pageable)

这里的关键是使用:/*#{#pageable}*/,它允许我进行排序和分页。您可以通过使用类似以下内容进行测试:http://localhost:8080/api/v1/entities/search/namespaceAndNameAndRawStateContainsMostRecentVersion?namespace=&name=&state=published&page=0&size=3&sort=name,desc。请注意此问题:Spring Pageable不会翻译@Column名称

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接