带有分页的 Spring Data 和 Native Query

Posted

技术标签:

【中文标题】带有分页的 Spring Data 和 Native Query【英文标题】:Spring Data and Native Query with pagination 【发布时间】:2016-11-15 22:30:51 【问题描述】:

在一个 web 项目中,使用最新的 spring-data (1.10.2) 和 mysql 5.6 数据库,我正在尝试使用带有分页的本机查询,但我在启动时遇到了 org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException

更新:20180306 此问题现已在Spring 2.0.4 中修复。对于那些仍然感兴趣或坚持使用旧版本的用户,请查看相关答案和 cmets 以了解解决方法。

根据Example 50 at Using @Query from spring-data documentation,可以指定查询本身和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,但它也在queryString 中寻找#pageable#sort 序列,我没有提供。

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

有趣的是,如果我在运行时手动将 containsPageableOrSortInQueryExpressionfalse 更改为 true,则查询工作正常,所以我不知道为什么它会检查该字符串是否位于我的 queryString 和我不知道如何提供。

任何帮助将不胜感激。

2018 年 1 月 30 日更新 spring-data 项目的开发人员似乎正在通过PR by Jens Schauder 解决此问题

【问题讨论】:

不,我还没有找到解决方案。我在 Spring JIRA 中创建了一张票,但没有回复:jira.spring.io/browse/DATAJPA-928。最后,我不需要分页,所以我没有尝试进一步调查或用那张票更努力地推动。 好的,谢谢。作为一种解决方法,您可以添加“\n#pageable\n”而不是“#pageable”,但我希望它会在未来得到修复。 当前的修复让我的情况变得更糟。现在我的 PageAble 参数被完全忽略,查询无限执行。因此,如果您插入了该 \n#pageable\n 解决方法,请确保将其删除,否则您会遇到麻烦。 【参考方案1】:

我提前道歉,这几乎是对原始问题和Janar的评论的总结,但是......

我遇到了同样的问题:我发现 Example 50 of Spring Data 是我需要使用分页进行本机查询的解决方案,但 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);

使用Page&lt;Author&gt;需要countQuery(在代码块中注释掉) 作为查询的返回类型,需要“#pageable”注释周围的换行符来避免预期参数数量的运行时错误(变通方法的变通方法)。我希望这个错误将很快得到修复...

【讨论】:

在我的情况下,?##pageable 正在工作,而不是 \n#pageable\n 这行得通。对于参数,您仍然可以使用命名参数。例如:authorId。对于 postgresql,我将您的 \n#pageable\n 更改为 --#pageable\n。正如您所建议的那样, countQuery 是必需的,因为许多示例使用 List 而不是 Page 您的答案就是针对此问题的。我浏览了相同的文档,如果不是您的回答,我会放弃。 如果您不编写 countQuery 并让框架处理它,有时不会为您正确编写按查询的计数,您可能会看到错误为无效字段列表。您始终可以通过将以下条目添加到属性文件 logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 来分析 SQL 语句,分别通过查询添加计数将解决问题。这可能取决于您使用的框架版本和数据库。 就我而言,只有/*:pageable*/ 有效(使用 SQL Server、Hibernate 5.4.1.Final 和 Spring Boot 2.2.1)【参考方案2】:

这是对使用 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 用于PageablecountQuery 代表Page&lt;User&gt;

【讨论】:

我现在不知道。尝试 1. 启用 SQL 语句的日志记录 - 在 application.properties 中设置 spring.jpa.show-sql=false; 2. 在您的 nativeQuery 中设置?##pageable 和 3. 从日志中分析结果 SQL。 @Lasneyx 针对 Spring Data JPA 的这种特性创建了问题 DATAJPA-928。【参考方案3】:

仅作记录,使用 H2 作为测试数据库,并在运行时使用 MySQL,这种方法有效(例如 newest object in group):

@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)。

【讨论】:

为 PostgreSQL 工作。谢谢! 如果我们必须通过 asc 或 desc 动态地将 order 传递给查询怎么办?【参考方案4】:

试试这个:

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 notation

【讨论】:

某种原因分类查询在可分页后有逗号,例如“ORDER BY /*#pageable*/”,导致语法错误 我删除了订单,但生成了异常意外字符:'#'【参考方案5】:

我的症状与@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。 @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 的数据类型【参考方案6】:

我使用 oracle 数据库,但没有得到结果,但生成的逗号出现错误,这是 d-man 上面所说的。

然后我的解决方案是:

Pageable pageable = new PageRequest(current, rowCount);

正如您在创建 Pagable 时看到的,没有顺序。

以及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);
 

【讨论】:

【参考方案7】:

我添加这个答案只是作为那些使用 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&lt;Object[]&gt;,有时这是必要的)。

【讨论】:

这对我有用,避免在选择查询中使用表别名,它不起作用!【参考方案8】:

以下两种方法都适用于 MySQL 对本机查询进行分页。但它们不适用于 H2。会报sql语法错误。

订购方式 ?##pageable 按 a.id 排序\n#pageable\n

【讨论】:

【参考方案9】:

我可以成功地将 Pagination 集成到

spring-data-jpa-2.1.6

如下。

@Query(
 value = “SELECT * FROM Users”, 
 countQuery = “SELECT count(*) FROM Users”, 
 nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);

【讨论】:

原生查询中的pageable参数在哪里?【参考方案10】:

使用“ORDER BY id DESC \n-- #pageable\n” 而不是“ORDER BY id \n#pageable\n”为我使用 MS SQL SERVER

【讨论】:

【参考方案11】:

它的工作原理如下:

public interface UserRepository extends JpaRepository<User, Long> 
    @Query(value = "select * from (select (@rowid\\:=@rowid+1) as RN, u.* from USERS u, (SELECT @rowid\\:=0) as init where  LASTNAME = ?1) as total"+
        "where RN between ?##pageable.offset-1 and ?##pageable.offset + #pageable.pageSize",
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
    Page<User> findByLastname(String lastname, Pageable pageable);

【讨论】:

【参考方案12】:

对我来说,下面是在 MS SQL 中工作的

 @Query(value="SELECT * FROM ABC r where r.type in :type  ORDER BY RAND() \n-- #pageable\n ",nativeQuery = true)
List<ABC> findByBinUseFAndRgtnType(@Param("type") List<Byte>type,Pageable pageable);

【讨论】:

【参考方案13】:

我正在使用下面的代码。工作

@Query(value = "select * from user usr" +
  "left join apl apl on usr.user_id = apl.id" +
  "left join lang on lang.role_id = usr.role_id" +
  "where apl.scr_name like %:scrname% and apl.uname like %:uname and usr.role_id in :roleIds ORDER BY ?##pageable",
  countQuery = "select count(*) from user usr" +
      "left join apl apl on usr.user_id = apl.id" +
      "left join lang on lang.role_id = usr.role_id" +
      "where apl.scr_name like %:scrname% and apl.uname like %:uname and usr.role_id in :roleIds",
  nativeQuery = true)
Page<AplUserEntity> searchUser(@Param("scrname") String scrname,@Param("uname") String  uname,@Param("roleIds") List<Long> roleIds,Pageable pageable);

【讨论】:

请你解释一下为什么这个答案比已经存在了几年的其他答案更好?【参考方案14】:

从查询和计数查询中删除 \n#pageable\n 对我有用。 Springboot 版本:2.1.5.RELEASE 数据库:Mysql

【讨论】:

【参考方案15】:

这在 Groovy 中对我有用(我正在使用 Postgres):

@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 does not translate @Column name

【讨论】:

【参考方案16】:

您可以将以下代码用于 h2 和 MySQl

    @Query(value = "SELECT req.CREATED_AT createdAt, req.CREATED_BY createdBy,req.APP_ID appId,req.NOTE_ID noteId,req.MODEL model FROM SUMBITED_REQUESTS  req inner join NOTE note where req.NOTE_ID=note.ID and note.CREATED_BY= :userId "
        ,
         countQuery = "SELECT count(*) FROM SUMBITED_REQUESTS req inner join NOTE note WHERE req.NOTE_ID=note.ID and note.CREATED_BY=:userId",
        nativeQuery = true)
Page<UserRequestsDataMapper> getAllRequestForCreator(@Param("userId") String userId,Pageable pageable);

【讨论】:

【参考方案17】:

您可以使用以下代码来实现它,

@Query(value = "SELECT * FROM users u WHERE  ORDER BY ?##pageable", nativeQuery = true)
List<User> getUsers(String name, Pageable pageable);

只需使用 ORDER BY ?##pageable 并将页面请求传递给您的方法。

享受吧!

【讨论】:

【参考方案18】:

将 /#pageable/ 替换为 ?##pageable 允许进行分页。 添加 PageableDefault 允许您设置页面元素的大小。

【讨论】:

以上是关于带有分页的 Spring Data 和 Native Query的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Data JPA 中使用带有分页的投影接口?

带有分页的 Spring-Data FETCH JOIN 不起作用

带有可分页的 Spring 自定义查询

spring data jpa 分页查询

使用带有mysql的spring MVC实现分页的最佳方法是啥

使用 Spring Data 中的 CassandraRepository 为 Cassandra 实现分页的正确方法