Spring Data y Native Query con paginación


86

En un proyecto web, usando los últimos datos de primavera (1.10.2) con una base de datos MySQL 5.6, estoy tratando de usar una consulta nativa con paginación pero estoy experimentando un org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException error al inicio.

ACTUALIZACIÓN : 20180306 Este problema ahora se solucionó en Spring 2.0.4 Para aquellos que aún están interesados ​​o estancados con versiones anteriores, consulte las respuestas y comentarios relacionados para encontrar soluciones.

De acuerdo con el Ejemplo 50 en Usar @Query de la documentación de datos de primavera, esto es posible especificando la consulta en sí y un countQuery, así:

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);
}

Por curiosidad, en NativeJpaQueryclase puedo ver que contiene el siguiente código para verificar si es una consulta jpa válida:

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);
   }
}

Mi consulta contiene un Pageableparámetro, también lo hasPagingOrSortingParameteres true, pero también busca una secuencia #pageableo #sortdentro de queryString, que no proporciono.

Intenté agregar #pageable(es un comentario) al final de mi consulta, lo que hace que la validación pase, pero luego, falla en la ejecución y dice que la consulta espera un parámetro adicional: 3 en lugar de 2.

Lo curioso es que, si cambio manualmente containsPageableOrSortInQueryExpressionde falsea truedurante la ejecución, la consulta funciona bien, por lo que no sé por qué está verificando que esa cadena esté en mi queryStringy no sé cómo proporcionarla.

Cualquier ayuda será muy apreciada.

Actualización 30/01/2018 Parece que los desarrolladores del proyecto spring-data están trabajando en una solución para este problema con un PR de Jens Schauder


No, todavía no he encontrado una solución. Creé un ticket en Spring JIRA pero no hay respuesta: jira.spring.io/browse/DATAJPA-928 . Al final, no necesité paginación, así que no he intentado investigar más ni presionar más con ese ticket.
Lasneyx

3
Bien gracias. Como solución alternativa, podría haber agregado "\ n # paginable \ n" en lugar de "# paginable", pero espero que se solucione en el futuro.
Janar

1
esa solución actual empeoró las cosas para mí. ahora mi parámetro PageAble se ignora por completo y la consulta se ejecuta de forma ilimitada. así que si insertó esa \ n # solución paginable \ n, asegúrese de eliminarla o tendrá problemas.
Rüdiger Schulz

Respuestas:


48

Mis disculpas de antemano, esto resume bastante bien la pregunta original y el comentario de Janar , sin embargo ...

Me encuentro con el mismo problema: encontré el Ejemplo 50 de Spring Data como la solución para mi necesidad de tener una consulta nativa con paginación, pero Spring se quejaba al inicio de que no podía usar la paginación con consultas nativas.

Solo quería informar que logré ejecutar con éxito la consulta nativa que necesitaba, usando paginación, con el siguiente código:

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

El countQuery (que está comentado en el bloque de código) es necesario para usarlo Page<Author> como el tipo de retorno de la consulta, las nuevas líneas alrededor del comentario "#pageable" son necesarias para evitar el error de tiempo de ejecución en el número de parámetros esperados (solución alternativa del solución alterna). Espero que este error se solucione pronto ...


1
En mi caso ?#{#pageable}está funcionando en cambio \n#pageable\n.
Dmitry Stolbov

6
Esto funciona. Para los argumentos, aún puede usar parámetros con nombre. ejemplo :authorId. Cambié tu \n#pageable\na --#pageable\npor postgresql. El countQuery es necesario como sugirió ya que muchos ejemplos usan List <> en lugar de Page <> su respuesta fue acertada para esto. Revisé los mismos documentos y si no fuera por tu respuesta, simplemente me habría rendido.
Abhishek Dujari

2
Si no escribe el countQuery y deja que el marco lo maneje, en algún momento no escribe el recuento por consulta correctamente y puede ver un error como lista de campos no válidos. Siempre puede analizar las declaraciones SQL agregando las siguientes entradas al archivo de propiedades logging.level.org.hibernate.SQL = DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder = TRACE Agregar el recuento por consulta por separado hará solucionar el problema. Esto puede depender de las versiones del marco y la base de datos que esté utilizando.
Nalaka

En mi caso solo /*:pageable*/funciona (con SQL Server, Hibernate 5.4.1.Final y Spring Boot 2.2.1)
marioosh

35

Este es un truco para un programa que usa Spring Data JPA antes de la versión 2.0.4 .

El código ha funcionado con PostgreSQL y 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}es para Pageable. countQueryes para Page<User>.


1
No se. 1. Trate habilitar el registro de sentencias SQL - conjunto spring.jpa.show-sql=falsede application.properties; 2. establecer ?#{#pageable}en su nativeQuery y 3. analizar el resultado SQL del registro. @Lasneyx creó el problema DATAJPA-928 para esta peculiaridad de Spring Data JPA.
Dmitry Stolbov

12

Solo para el registro, usando H2 como base de datos de prueba y MySQL en tiempo de ejecución, este enfoque funciona (el ejemplo es el objeto más nuevo en el grupo ):

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


Trabajó para PostgreSQL. ¡Gracias!
Maksym Chernikov

¿Qué pasa si tenemos que pasar orden por asc o desc dinámicamente a la consulta?
Kulbhushan Singh

8

Tengo exactamente el mismo síntoma que @Lasneyx. Mi solución para la consulta nativa de 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);

También funcionó para mí con DB2.
Anders Metnik

@Query (nativeQuery = true, value = "seleccione a. * De rqst a LEFT OUTER JOIN table_b b ON a.id = b.id donde a.code = 'abc' ORDER BY - # pageable \ n") no es trabajando: No se pudo determinar el tipo de datos del parámetro $ 2
Desarrollador

7

Prueba esto:

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);
}

( "/* */"para Oracle notation)


por alguna razón, la consulta generada tiene una coma después de paginable, algo como esto "ORDER BY / * # paginable * /", lo que provoca un error de sintaxis
d-man


5

Utilizo la base de datos de Oracle y no obtuve el resultado, sino un error con la coma generada del que d-man habló anteriormente.

Entonces mi solución fue:

Pageable pageable = new PageRequest(current, rowCount);

Como puede ver sin orden al crear Pagable.

Y el método en el 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);
 }

1

Los dos enfoques siguientes funcionan bien con MySQL para paginar consultas nativas. Sin embargo, no funcionan con H2. Se quejará del error de sintaxis sql.

  • PEDIR POR? # {# Pageable}
  • ORDENAR POR a.id \ n # paginable \ n

1

Usar "ORDER BY id DESC \ n-- #pageable \ n" en lugar de "ORDER BY id \ n # pageable \ n" funcionó para mí con MS SQL SERVER


1

Podría integrar con éxito la paginación en

primavera-datos-jpa-2.1.6

como sigue.

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

0

Funciona de la siguiente manera:

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);
}

0

Para mí a continuación trabajó en 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);

0

Estoy usando el siguiente código. trabajando

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

1
¿Puede explicar por qué esta respuesta es mejor que cualquiera de las otras que han existido durante varios años?
Dragon Thoughts

0

Eliminar \ n # pageable \ n tanto de la consulta como de la consulta de recuento funcionó para mí. Versión de Springboot: 2.1.5.RELEASE DB: Mysql


0

Esto funcionó para mí (estoy usando Postgres) en 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)

La clave aquí fue usar: /*#{#pageable}*/

Me permite ordenar y paginar. Puedes probarlo usando algo como esto: http: // localhost: 8080 / api / v1 / entity / search / namespaceAndNameAndRawStateContainsMostRecentVersion? Namespace = & name = & state = Published & page = 0 & size = 3 & sort = name, desc

Tenga cuidado con este problema: Spring Pageable no traduce el nombre de @Column


0

Puede usar el siguiente código para h2 y 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);

-1

Reemplazar / #pageable / con? # {# Pageable} permite hacer paginación. Agregar PageableDefault le permite establecer el tamaño de los elementos de la página.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.