Java >> Java tutorial >  >> Tag >> native

EntityManager.createNativeQuery returnerer liste over objekter i stedet for liste med BigDecimal ved brug af sideinddeling

Det problem, du støder på, er, at din OracleDialect tilføjer en kolonne til dets valgte resultatsæt. Den omslutter den forespørgsel, du kører, som beskrevet i SternKs svar.

Hvis du brugte Hibernate SessionFactory og Session-grænsefladen, så ville den funktion, du leder efter, være "addScalar"-metoden. Desværre ser der ikke ud til at være en implementering i ren JPA (se spørgsmålet her:Har JPA en ækvivalent til Hibernate SQLQuery.addScalar()?).

Jeg forventer, at din nuværende implementering fungerer fint i DB2, H2, HSQL, Postgres, MySQL (og et par andre DB-motorer). Men i Oracle tilføjer den en rækkenummerkolonne til ResultSet, hvilket betyder, at Hibernate får 2 kolonner fra ResultSet. Hibernate implementerer ikke nogen forespørgselsparsing i dette tilfælde, hvilket betyder, at det blot parser ResultSet ind i din liste. Da den får 2 værdier, konverterer den dem til et Objekt[] i stedet for en BigDecimal.

Som en advarsel er det lidt farligt at stole på JDBC-driveren til at levere den forventede data-type, da Hibernate vil spørge JDBC-driveren, hvilken datatype den foreslår. I dette tilfælde foreslår det en BigDecimal, men under visse betingelser og visse implementeringer vil det være tilladt at returnere en Double eller en anden type.

Så har du et par muligheder.

  1. Du kan ændre din orakel-dialekt (som SternK) foreslår. Dette vil drage fordel af en alternativ Oracle-paging-implementering.

  2. Hvis du ikke er imod at have dvale-specifikke aspekter i din JPA-implementering, så kan du drage fordel af yderligere dvalefunktioner, som ikke tilbydes i JPA-standarden. (Se følgende kode...)

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

Dette har den fordel, at det eksplicit fortæller hibnerate, at du kun er interesseret i "id"-kolonnen i dit ResultSet, og at dvaletilstand eksplicit skal konvertere til det returnerede objekt til en BigDecimal, hvis JDBC-driveren beslutter, at en anden type ville være mere passende som standard.


Grundårsagen til dit problem er den måde, hvordan paginering implementeret på din orakeldialekt i dvale.

Der er to tilfælde:

  1. Når vi har setFirstResult(0) følgende sql vil blive genereret:
-- setMaxResults(5).setFirstResult(0)
select * from (
  select test_id from TST_MY_TEST -- this is your initial query
) 
where rownum <= 5;

Som du kan se, returnerer denne forespørgsel nøjagtig den samme kolonneliste som din oprindelige forespørgsel, og du har derfor ikke problemer med denne sag.

  1. Når vi indstiller setFirstResult i ikke 0 værdi vil følgende sql blive genereret:
-- 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

Som du kan se, returnerer denne forespørgsel kolonnelisten med yderligere rownum_ kolonne, og derfor har du problemet med at caste dette resultatsæt til BigDecimal .

Løsning

Hvis du bruger Oracle 12c R1 (12.1) eller højere, kan du tilsidesætte denne adfærd i din dialekt ved at bruge en ny rækkebegrænsende klausul på denne måde:

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

og brug det derefter.

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

Til mit testdatasæt for følgende forespørgsel:

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

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

Jeg fik:

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.S. Se også HHH-12087

P.P.S Jeg forenklede min implementering af AbstractLimitHandler ved at fjerne checkende gaver FOR UPDATE klausul. Jeg tror, ​​vi ikke vil have noget godt i denne sag og med denne kontrol.

For eksempel for følgende tilfælde:

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

dvale (med Oracle12cDialect ) vil generere følgende 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

Som du kan se, forsøger dvaletilstand at rette forespørgslen ved at flytte FOR UPDATE til slutningen af ​​forespørgslen. Men alligevel får vi:

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

Jeg har simuleret din konsultation, og alt fungerer fint. Jeg har brugt DataJpaTest til forekomst af entityManager for mig, h2 hukommelsesdatabase og JUnit 5 at køre testen. Se nedenfor:

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

Jeg foreslår, at du gennemgår din oprindelige forespørgsel. Det er at foretrække, at du bruger Criteria API i stedet og lader indbyggede forespørgsler til ekstreme tilfælde som komplekse konsultationer.

Opdater

Efter at forfatteren havde postet projektet, kunne jeg gengive problemet, og det var relateret til orakeldialekten. Af ukendt årsag er forespørgslen, der kører for det andet opkald:select * from ( select row_.*, rownum rownum_ from ( SELECT c.SHOP_ID FROM CUSTOMER c ) row_ where rownum <= ?) where rownum_ > ? , og det er derfor, det genererer en fejl, fordi det spørger efter 2 kolonner i stedet for kun én. Den uønskede er denne rownum . For andre dialekter er der ikke noget sådant problem.

Jeg foreslår, at du prøver en anden orakeldialektversion, og om ingen af ​​dem virker, er mit sidste tip at prøve at lave sideinddelingen selv.


Java tag