sourcetip

EntityManager.createNativeQuery가 페이지화를 사용할 때 BigDecimal 목록 대신 개체 목록을 반환합니다.

fileupload 2023. 10. 10. 20:55
반응형

EntityManager.createNativeQuery가 페이지화를 사용할 때 BigDecimal 목록 대신 개체 목록을 반환합니다.

와 함께 .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다지)가 표시됩니다. 예상되는 BigDecimal 목록이 표시됩니다.

.pageNumber0 ( 두 이 의 각 에는 두 첫 두 이인 것 0 예:지),데,의 BigDecimal데,에는 db고, 두 번째 BigDecimal은 이 행의 위치인 것 같습니다.

그리고 분명히 나는 이 예외를 가지고 있습니다.

자바 langClassCastException: class [Ljava.lang.개체; 클래스 java.math에 캐스트할 수 없습니다.빅 데시말

누가 이 불일치를 설명해 줄 수 있나요? 그리고 이것이 빅 십진법 목록을 항상 반환하기 위해 어떻게 수정될 수 있는지 설명해 주실 수 있나요?감사해요.

업데이트-1 : 이 문제를 재현하기 위해 샘플 프로젝트를 만들었습니다.오라클 데이터베이스만으로 이 문제를 재현할 수 있었습니다.H2 데이터베이스를 사용하면 잘 작동했고, 페이지 번호와 상관없이 BigDecimal 목록을 지속적으로 얻었습니다.

업데이트-2 : H2로 샘플 프로젝트를 제작하여 문제없이 작동합니다.

OracleDialct에서 선택한 ResultSet에 열을 추가하는 문제가 발생합니다.SternK의 답변에서 논의된 대로 실행 중인 쿼리를 요약합니다.

Hibernate SessionFactory와 Session 인터페이스를 사용하는 경우 원하는 함수는 "addScalar" 메서드일 것입니다.안타깝게도 순수 JPA에는 구현이 없는 것 같습니다(여기 질문 참조: JPA에 Hibernate SQLQuery.addScalar()와 동등한 기능이 있습니까?).

DB2, H2, HSQL, Postgres, MySQL(및 다른 몇 개의 DB 엔진)에서 귀사의 현재 구현이 잘 작동할 것으로 기대합니다.그러나 Oracle에서는 ResultSet에 행 번호 열을 추가하여 Hibernate가 ResultSet에서 2개의 열을 가져옵니다.Hibernate는 이 경우 쿼리 구문 분석을 구현하지 않습니다. 즉, ResultSet을 List에 구문 분석하기만 하면 됩니다.2개의 값을 얻기 때문에 BigDecimal이 아닌 Object[]로 변환됩니다.

Hibernate는 JDBC 드라이버에게 어떤 데이터 유형을 제안하는지 묻기 때문에 JDBC 드라이버에 의존하여 예상 데이터 유형을 제공하는 것은 다소 위험합니다.이 경우 Big Decimal을 제안하지만 특정 조건 및 특정 구현에서는 Double 또는 다른 유형을 반환할 수 있습니다.

그럼 몇 가지 선택 사항이 있습니다.

  1. SternK가 제안하는 대로 Oracle-Dialogue를 수정할 수 있습니다.이를 통해 대체 오라클 페이징 구현을 활용할 수 있습니다.

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

이것은 하이브너레이트를 명시적으로 알려줄 수 있으며, 결과 집합의 "id" 열에만 관심이 있으며, 하이브너레이트는 JDBC-드라이버가 기본값으로 다른 유형이 더 적합하다고 결정할 경우 반환된 개체를 BigDecimal로 명시적으로 변환해야 한다는 장점이 있습니다.

문제의 근본 원인은 페이지가 최대 절전 모드 오라클 방언으로 구현된 방식입니다.

두 가지 경우가 있습니다.

  1. 가 때.setFirstResult(0)다음 sql이 생성됩니다.
-- setMaxResults(5).setFirstResult(0)
select * from (
  select test_id from TST_MY_TEST -- this is your initial query
) 
where rownum <= 5;

보시다시피 이 쿼리는 초기 쿼리와 정확히 동일한 열 목록을 반환하므로 이 경우에는 문제가 없습니다.

  1. 할 때.setFirstResult그렇지 않은0 value음 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

추신: HHH-12087도 참조

. 의했습니다 했습니다.AbstractLimitHandlerFOR UPDATE이 되지 .이 경우와 이번 점검으로는 아무런 도움이 되지 않을 것 같습니다.

예를 들어 다음과 같은 경우에 해당합니다.

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

자다Oracle12cDialect과 같은 sql 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를 대신 사용하고 네이티브 쿼리를 허용하는 것이 좋습니다.

갱신하다

저자가 프로젝트를 올린 후 문제를 재현할 수 있었고 오라클 방언과 관련이 있었습니다.알 수 없는 이유로 두 번째 호출에 대해 실행 중인 쿼리는 다음과 같습니다.select * from ( select row_.*, rownum rownum_ from ( SELECT c.SHOP_ID FROM CUSTOMER c ) row_ where rownum <= ?) where rownum_ > ?, 이것이 버그를 발생시키는 이유는 하나의 열이 아닌 2개의 열을 쿼리하기 때문입니다.원치 않는 것은 이것입니다.rownum. 다른 방언의 경우에는 그런 문제가 없습니다.

다른 오라클 방언 버전을 사용해보시기를 권합니다. 그 중 어느 것도 작동하지 않든 간에, 저의 마지막 팁은 페이지를 직접 해보는 것입니다.

봄 도서관의 다양한 버전들로 많은 추적 끝에, 저는 마침내 그 문제를 알아낼 수 있었습니다.제가 시도한 것 중 하나는 v2.1.5에서 spring-data-commons 라이브러리를 업데이트하자마자 문제가 사라진 것 같습니다.v2.1.6으로 릴리스.풀어주다.이 릴리스의 변경 로그를 찾아봤는데, 스프링 데이터 커먼의 이 버그와 관련된 이 버그가 이 문제의 근본 원인입니다.스프링 데이터 커먼 라이브러리를 업그레이드한 후 문제를 해결할 수 있었습니다.

언급URL : https://stackoverflow.com/questions/63738889/entitymanager-createnativequery-returning-list-of-objects-instead-of-list-of-big

반응형