![](/img/trans.png)
[英]How to not replace named parameters in EntityManager.createNativeQuery()?
[英]EntityManager.createNativeQuery returning list of objects instead of list of BigDecimal when using Pagination
我正在嘗試將分頁與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(第一頁)時,我得到預期的 BigDecimals 列表:
但是只要pageNumber
> 0(例如,第二頁),我就會得到一個對象列表,這個列表中的每個對象似乎都包含兩個 BigDecimal,其中第一個包含來自數據庫的值,第二個 BigDecimal 似乎包含成為這一行的位置。
顯然我得到了這個例外
java.lang.ClassCastException: 類 [Ljava.lang.Object; 不能強制轉換為 java.math.BigDecimal 類
有人可以解釋這種差異,以及如何修復它以始終返回 BigDecimals 列表? 謝謝你。
更新 1 :我創建了一個示例項目來重現此問題。 我只能使用 Oracle 數據庫重現此問題。 使用 H2 數據庫,它運行良好,並且我始終得到一個與頁碼無關的 BigDecimals 列表。
您遇到的問題是您的 OracleDialect 將一列添加到其選定的 ResultSet。 它包裝了您在 SternK 的回答中討論的正在運行的查詢。
如果您正在使用 Hibernate SessionFactory 和 Session 接口,那么您要尋找的函數將是“addScalar”方法。 不幸的是,在純 JPA 中似乎沒有實現(請參閱此處提出的問題: Does JPA has an equivalent to Hibernate SQLQuery.addScalar()? )。
我希望您當前的實現能夠在 DB2、H2、HSQL、Postgres、MySQL(以及其他一些數據庫引擎)中正常工作。 但是,在 Oracle 中,它將行號列添加到 ResultSet,這意味着 Hibernate 從 ResultSet 中獲取 2 列。 在這種情況下,Hibernate 沒有實現任何查詢解析,這意味着它只是將 ResultSet 解析到您的 List 中。 由於它獲得 2 個值,因此它將它們轉換為 Object[] 而不是 BigDecimal。
需要注意的是,依賴 JDBC 驅動程序提供預期數據類型有點危險,因為 Hibernate 會詢問 JDBC 驅動程序它建議的數據類型。 在這種情況下,它建議使用 BigDecimal,但在某些條件下和某些實現將被允許返回 Double 或其他一些類型。
那么你有幾個選擇。
您可以修改您的 oracle 方言(如 SternK 建議的那樣)。 這將利用替代的 oracle-paging 實現。
如果您不反對在 JPA 實現中使用特定於休眠的方面,那么您可以利用 JPA 標准中未提供的其他休眠功能。 (見下面的代碼...)
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);
這確實具有顯式告訴 hibernate 的優點,您只對 ResultSet 的“id”列感興趣,並且 hibernate 需要顯式地將返回的對象轉換為 BigDecimal,如果 JDBC 驅動程序決定不同的類型作為默認值會更合適。
您的問題的根本原因在於分頁如何在您的 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) 或更高版本,您可以通過這種方式使用新的行限制子句在您的方言中覆蓋此行為:
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
PS 另見HHH-12087
PPS 我通過刪除檢查顯示FOR UPDATE
子句簡化了AbstractLimitHandler
的實現。 我認為在這種情況下,通過這次檢查,我們不會有任何好處。
例如對於以下情況:
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.
我已經模擬了您的咨詢,一切正常。 我已經使用DataJpaTest
為我實例化 entityManager、 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);
}
我建議您查看您的本機查詢。 最好使用 Criteria API,並讓原生查詢用於極端情況,例如復雜的咨詢。
更新
作者發項目后,可以復現問題,和oracle方言有關。 由於未知原因,第二次調用運行的查詢是: select * from ( select row_.*, rownum rownum_ from ( SELECT c.SHOP_ID FROM CUSTOMER c ) row_ where rownum <= ?) where rownum_ > ?
,這就是為什么這會產生錯誤,因為它查詢 2 列而不是僅一列。 不受歡迎的是這個rownum
。 對於其他方言,沒有這樣的問題。
我建議您嘗試其他 oracle 方言版本,如果它們都不起作用,我的最后提示是嘗試自己進行分頁。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.