Saltar al contenido

EntityManager.createNativeQuery devuelve una lista de objetos en lugar de una lista de BigDecimal cuando se usa la paginación

Solución:

El problema con el que se está encontrando es que su OracleDialect agrega una columna a su ResultSet seleccionado. Envuelve la consulta que está ejecutando como se explica en la respuesta de SternK.

Si estuviera utilizando las interfaces SessionFactory y Session de Hibernate, entonces la función que estaría buscando sería el método “addScalar”. Desafortunadamente, no parece haber una implementación en JPA puro (vea la pregunta que se hace aquí: ¿JPA tiene un equivalente a Hibernate SQLQuery.addScalar ()?).

Espero que su implementación actual funcione bien en DB2, H2, HSQL, Postgres, MySQL (y algunos otros motores de base de datos). Sin embargo, en Oracle, agrega una columna de número de fila al ResultSet, lo que significa que Hibernate obtiene 2 columnas del ResultSet. Hibernate no implementa ningún análisis de consultas en este caso, lo que significa que simplemente analiza el ResultSet en su Lista. Como obtiene 2 valores, los convierte en un objeto.[] en lugar de un BigDecimal.

Como advertencia, confiar en el controlador JDBC para proporcionar el tipo de datos esperado es un poco peligroso, ya que Hibernate le preguntará al controlador JDBC qué tipo de datos sugiere. En este caso, sugiere un BigDecimal, pero bajo ciertas condiciones y ciertas implementaciones podrían devolver un Double o algún otro tipo.

Entonces tienes un par de opciones.

  1. Puede modificar su dialecto de oráculo (como sugiere SternK). Esto aprovechará una implementación de paginación de Oracle alternativa.

  2. Si no se opone a tener aspectos específicos de hibernación en su implementación de JPA, puede aprovechar las funciones de hibernación adicionales que no se ofrecen en el estándar JPA. (Ver el siguiente código …)

    List<BigDecimal> results = entitymanager.createNativeQuery("select distinct id from ... group by ... having ...")
            .unwrap(org.hibernate.query.NativeQuery.class)
            .addScalar("id", BigDecimalType.INSTANCE)
            .getResultList();
    System.out.println(results);
    

Esto tiene la ventaja de decirle explícitamente a hibnerate, que solo está interesado en la columna “id” de su ResultSet, y que hibernate necesita convertir explícitamente el objeto devuelto en un BigDecimal, en caso de que el controlador JDBC decida que un tipo diferente sería más apropiado por defecto.

La causa raíz de su problema en la forma en que se implementó la paginación en su dialecto de hibernación de Oracle.

Hay dos casos:

  1. Cuando nosotros tenemos setFirstResult(0) se generará el siguiente sql:
-- setMaxResults(5).setFirstResult(0)
select * from (
  select test_id from TST_MY_TEST -- this is your initial query
) 
where rownum <= 5;

Como puede ver, esta consulta devuelve exactamente la misma lista de columnas que su consulta inicial y, por lo tanto, no tiene problemas con este caso.

  1. Cuando nos ponemos setFirstResult en no 0 valor se generará el siguiente sql:
-- setMaxResults(5).setFirstResult(2)
select * from (
   select row_.*, rownum rownum_ 
   from (
      select test_id from TST_MY_TEST -- this is your initial query
   ) row_ 
   where rownum <= 5
) 
where rownum_ > 2

Como puede ver, esta consulta devuelve la lista de columnas con más rownum_ columna y, por lo tanto, tiene el problema de convertir este conjunto de resultados en el BigDecimal.

Solución

Si usa Oracle 12c R1 (12.1) o superior, puede anular este comportamiento en su dialecto usando una nueva cláusula de limitación de filas de esta manera:

import org.hibernate.dialect.Oracle12cDialect;
import org.hibernate.dialect.pagination.AbstractLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.engine.spi.RowSelection;


public class MyOracleDialect extends Oracle12cDialect
{
   private static final AbstractLimitHandler LIMIT_HANDLER = new AbstractLimitHandler() {
      @Override
      public String processSql(String sql, RowSelection selection) {
         final boolean hasOffset = LimitHelper.hasFirstRow(selection);
         final StringBuilder pagingSelect = new StringBuilder(sql.length() + 50);
         pagingSelect.append(sql);
         
         /*
            see the documentation https://docs.oracle.com/database/121/SQLRF/statements_10002.htm#BABHFGAA
            (Restrictions on the row_limiting_clause)
            You cannot specify this clause with the for_update_clause.
          */
         if (hasOffset) {
            pagingSelect.append(" OFFSET ? ROWS");
         }
         pagingSelect.append(" FETCH NEXT ? ROWS ONLY");
         return pagingSelect.toString();
      }

      @Override
      public boolean supportsLimit() {
         return true;
      }
   };

   public MyOracleDialect()
   {
   }
   
   @Override
   public LimitHandler getLimitHandler() {
      return LIMIT_HANDLER;
   }
}

y luego úselo.

<property name="hibernate.dialect">com.me.MyOracleDialect</property>

Para mi conjunto de datos de prueba para la siguiente consulta:

NativeQuery query = session.createNativeQuery(
   "select test_id from TST_MY_TEST"
).setMaxResults(5).setFirstResult(2);

List<BigDecimal> results = query.getResultList();

Tengo:

Hibernate: 
/* dynamic native SQL query */
select test_id  from TST_MY_TEST
OFFSET ? ROWS FETCH NEXT ? ROWS ONLY

val = 3
val = 4
val = 5
val = 6
val = 7

PS Consulte también HHH-12087

PPS simplifiqué mi implementación del AbstractLimitHandler quitando regalos de cheques FOR UPDATE cláusula. Creo que no tendremos nada bueno en este caso y con esta comprobación.

Por ejemplo para el siguiente caso:

NativeQuery query = session.createNativeQuery(
   "select test_id from TST_MY_TEST FOR UPDATE OF test_id"
).setMaxResults(5).setFirstResult(2);

hibernar (con Oracle12cDialect) generará el siguiente sql:

/* dynamic native SQL query */
select * from (
  select
     row_.*,
     rownum rownum_ 
  from (
     select test_id from TST_MY_TEST -- initial sql without FOR UPDATE clause
  ) row_ 
  where rownum <= 5
) 
where rownum_ > 2
FOR UPDATE OF test_id -- moved for_update_clause

Como puede ver, hibernate intenta arreglar la consulta moviendo FOR UPDATE al final de la consulta. Pero de todos modos, obtendremos:

ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc.

He simulado tu consulta y todo funciona bien. he usado DataJpaTest a la instancia entityManager para mí, h2 base de datos de memoria y JUnit 5 para ejecutar la prueba. Vea abajo:

@Test
public void shouldGetListOfSalaryPaginated() {
    // given
    Person alex = new Person("alex");
    alex.setSalary(BigDecimal.valueOf(3305.33));
    Person john = new Person("john");
    john.setSalary(BigDecimal.valueOf(33054.10));
    Person ana = new Person("ana");
    ana.setSalary(BigDecimal.valueOf(1223));
    
    entityManager.persist(alex);
    entityManager.persist(john);
    entityManager.persist(ana);
    entityManager.flush();
    entityManager.clear();

    // when
    List<BigDecimal> found = entityManager.createNativeQuery("SELECT salary FROM person").setMaxResults(2).setFirstResult(2*1).getResultList();

    // then
    Assertions.assertEquals(found.size(), 1);
    Assertions.assertEquals(found.get(0).longValue(), 1223L);
}

Le sugiero que revise su consulta nativa. Es preferible que utilice Criteria API en su lugar y permita consultas nativas para casos extremos como consultas complejas.

Actualizar

Después de que el autor publicó el proyecto, pude reproducir el problema y estaba relacionado con el dialecto del oráculo. Por una razón desconocida, la consulta que se está ejecutando para la segunda llamada es: select * from ( select row_.*, rownum rownum_ from ( SELECT c.SHOP_ID FROM CUSTOMER c ) row_ where rownum <= ?) where rownum_ > ?, y es por eso que esto genera un error, porque consulta 2 columnas en lugar de solo una. El indeseado es este rownum. Para otros dialectos no existe tal problema.

Le sugiero que pruebe otra versión de dialecto de Oracle y si ninguno de ellos funciona, mi consejo final es intentar hacer la paginación usted mismo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *