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.
-
Puede modificar su dialecto de oráculo (como sugiere SternK). Esto aprovechará una implementación de paginación de Oracle alternativa.
-
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:
- 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.
- Cuando nos ponemos
setFirstResult
en no0
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.