entitymanager.createnativequery在使用分页时返回对象列表,而不是bigdecimal列表

bfhwhh0e  于 2021-07-05  发布在  Java
关注(0)|答案(4)|浏览(810)

我正在尝试使用分页 EntityManager.createNativeQuery() . 下面是我正在使用的基本代码:

var query = em.createNativeQuery("select distinct id from ... group by ... having ...");
List<BigDecimal> results = query
        .setMaxResults(pageSize)
        .setFirstResult(pageNumber * pageSize)
        .getResultList();

什么时候 pageNumber 如果为0(第一页),我将得到预期的大小数列表:

但只要 pageNumber >0(例如,第二页),我得到一个对象列表,这个列表中的每个对象似乎都包含两个bigdecimal,第一个包含db中的值,第二个bigdecimal似乎是此行的位置。

很明显我有个例外
java.lang.classcastexception:类[ljava.lang.object;不能强制转换为类java.math.bigdecimal
有人能解释一下这种差异吗,以及如何修正这种差异以总是返回一个大小数列表?谢谢您。
更新1:我创建了一个示例项目来重现这个问题。我只能用oracle数据库重现这个问题。在h2数据库中,它运行得很好,我一直得到一个与页码无关的大小数列表。
update-2:我还创建了一个带有h2的示例项目,它可以在没有这个问题的情况下工作。

qqrboqgw

qqrboqgw1#

问题的根本原因在于如何在hibernate-oracle方言中实现分页。
有两种情况:
当我们有 setFirstResult(0) 将生成以下sql:

-- setMaxResults(5).setFirstResult(0)
select * from (
  select test_id from TST_MY_TEST -- this is your initial query
) 
where rownum <= 5;

如您所见,此查询返回的列列表与初始查询完全相同,因此您对这种情况没有问题。
当我们出发的时候 setFirstResult 在不 0 值将生成以下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

如您所见,此查询返回列列表,其中包含其他 rownum_ 列,因此将此结果集强制转换为 BigDecimal .
解决方案
如果您使用oracle 12c r1(12.1)或更高版本,您可以使用new row limiting子句以如下方式重写方言中的此行为:

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

然后使用它。

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

对于以下查询的“我的测试数据集”:

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

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

我得到了:

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

p、 另见hhh-12087
p、 另外,我简化了我对 AbstractLimitHandler 通过移除支票礼物 FOR UPDATE 条款。我想在这种情况下,我们不会有什么好的检查。
例如,对于以下情况:

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

休眠(使用 Oracle12cDialect )将生成以下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

如您所见,hibernate尝试通过移动 FOR UPDATE 到查询的结尾。但无论如何,我们会得到:

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

jfewjypa2#

我模拟了你的咨询,一切正常。我用过 DataJpaTest 给我举个实体经理的例子, h2 内存数据库和 JUnit 5 运行测试。见下表:

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

我建议您检查您的本地查询。最好改用criteriaapi,让本机查询用于复杂咨询等极端情况。
更新
在作者发布了这个项目之后,我可以重现这个问题,它与甲骨文方言有关。由于未知原因,正在为第二个调用运行的查询是: select * from ( select row_.*, rownum rownum_ from ( SELECT c.SHOP_ID FROM CUSTOMER c ) row_ where rownum <= ?) where rownum_ > ? ,这就是为什么它会生成一个bug,因为它查询的是两列而不是一列。不想要的是这个 rownum . 对于其他方言来说,没有这样的问题。
我建议您尝试其他oracle方言版本,无论它们是否都有效,我最后的建议是尝试自己进行分页。

hzbexzde

hzbexzde3#

您遇到的问题是,您的oracledialect向其选定的结果集添加了一列。它 Package 了您正在运行的查询,正如sternk的答案中所讨论的那样。
如果您使用的是hibernate sessionfactory和session接口,那么您要查找的函数将是“addscalar”方法。不幸的是,在纯jpa中似乎没有实现(请参阅这里提出的问题:jpa是否有与hibernate sqlquery.addscalar()等价的实现?)。
我希望您当前的实现在db2、h2、hsql、postgres、mysql(以及其他一些db引擎)中运行良好。但是,在oracle中,它向resultset中添加了一个行号列,这意味着hibernate从resultset中获得2列。在本例中,hibernate不实现任何查询解析,这意味着它只是将结果集解析到您的列表中。因为它得到2个值,所以它将它们转换为对象[],而不是bigdecimal。
作为警告,依赖jdbc驱动程序来提供预期的数据类型有点危险,因为hibernate会询问jdbc驱动程序它建议的数据类型。在本例中,它建议使用bigdecimal,但在某些条件下和某些实现将允许返回double或其他类型。
那你有几个选择。
你可以修改你的甲骨文方言(正如斯特恩克所建议的)。这将利用另一种oracle分页实现。
如果您不反对在jpa实现中使用hiberate特定方面,那么您可以利用jpa标准中没有提供的其他hibernate功能(请参阅以下代码…)

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

这样做的好处是可以显式地告诉hibnerate,您只对resultset的“id”列感兴趣,并且hibernate需要显式地将返回的对象转换为bigdecimal,如果jdbc驱动程序决定使用不同的类型作为默认值更合适的话。

e3bfsja2

e3bfsja24#

在对不同版本的spring库进行了大量的跟踪之后,我终于找到了问题所在。在我的一次尝试中,当我将spring数据共享库从v2.1.5.release更新到v2.1.6.release时,这个问题似乎已经消失了。我查阅了这个版本的changelog,这个bug与springdatacommons中的这个bug有关,是这个问题的根本原因。在升级spring数据共享库之后,我能够解决这个问题。

相关问题