2013-03-15 20 views
7

Ho il seguente codice che esegue una semplice query da una singola tabella di un database Oracle.Semplificazione della query di Hibernate

entityManager.createQuery(
     "SELECT a FROM " + Person.class.getSimpleName() 
     + " a WHERE lower(a.firstName) = '" + firstName + "'") 
     .getSingleResult(); 

Hibernate produce il seguente sql:

select 
     * 
    from 
     (select 
      person0_.id as id75_, 
      person0_.FIRSTNAME as FIRSTNAME75_, 
      person0_.LASTNAME as LASTNAME75_ 
     from 
      PERSONS person0_ 
     where 
      lower(person0_.FIRSTNAME)='john') 
    where 
     rownum <= ? 

nostro DBA suggerisce che questa query deve essere più semplice per motivi di prestazioni. Come posso fare hibernate per semplificare la query in questo modo:

select ID, FIRSTNAME, LASTNAME from PERSONS 
where lower(FIRSTNAEM) = 'john' and rownum <= 1 

Grazie

+0

prima di tutto vorrei creare un indice sul campo FIRSTNAME. –

+1

@StefanBe: e tale indice verrebbe ignorato poiché la clausola where utilizza una funzione. Avresti bisogno di un indice funzionale ... – beny23

+0

ah ok. non so che –

risposta

-2

In breve, non è così. Non c'è quasi mai una ragione sufficiente per modificare le query generate da Hibernate, e dubito seroius che sia possibile senza modificare il codice sorgente.

L'unica semplificazione che posso pensare è chiamare getResultList().get(0) anziché getSingleResult(). Semplificherebbe un po 'l'SQL generato, ma determinerebbe un impatto sulle prestazioni, non un miglioramento, dal momento che recupererai tutte le righe corrispondenti dal DB.

Si potrebbe tuttavia migliorare la query un po '.

Si conosce il nome semplice della classe per la quale si sta eseguendo la query. Non v'è alcuna necessità di ottenere da Entità prima e concatenare, è sufficiente utilizzare

SELECT a FROM Person a...

parametri Concatenazione per una query non è una buona pratica.Rende la tua query vulnerabile agli attacchi di SQL injection. E 'meglio scrivere:

...WHERE lower(a.firstName) = :firstName"); 
query.setParameter("firstName", firstName); 
+0

Penso che chiamare 'getResultList(). get (0)' è una cattiva idea, in quanto si restituirebbero tutte le righe dal database solo per il proprio codice per usare solo la prima riga. Le prestazioni saranno migliori per consentire a Oracle di restituire una sola riga. – beny23

+0

@ beny23 sì, sicuramente peggiorerà le prestazioni, ma semplificherà un po 'la query. L'ho reso più esplicito nella risposta ora, non era chiaro. – kostja

+0

Cosa succede se getResultList() restituisce 1 riga utilizzando un campo univoco nella clausola where? –

8

Ho appena guardato il explain plan per le query simili ai tuoi e il piano è esattamente lo stesso per entrambe le query, in modo Non sono sicuro di quali siano le prestazioni che il tuo DBA suggerisce.

Il wrapping della query con select * from (...) where rownum = 1 introduce uno STOPKEY che interrompe la query interna dopo una riga. Oracle sa che in realtà non vuoi ottenere tutti i risultati dalla subquery e quindi prendere solo la prima riga.

Cambiare la query prodotta da Hibernate sarà impossibile senza modificare il codice sorgente di ibernazione stesso.

nota, il motivo per cui questo nidificazione è necessario diventa evidente quando si tenta di introdurre una ORDER BY clausola:

select ID, FIRSTNAME, LASTNAME 
    from PERSONS 
where lower(FIRSTNAME) = 'john' 
    and rownum <= 1 
order by LASTNAME 

produce risultati diversi a

select * from (
    select ID, FIRSTNAME, LASTNAME 
     from PERSONS 
    where lower(FIRSTNAME) = 'john' 
    order by LASTNAME) 
    where rownum <= 1 

come where rownum viene applicato prima della order by clause ....

MODIFICA:

Per riferimento ecco l'output del piano spiegare, e che è esattamente lo stesso per entrambe le query:

--------------------------------------------------------------------------------- 
| Id | Operation   | Name  | Rows | Bytes | Cost (%CPU)| Time  | 
--------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |   |  1 | 112 |  2 (0)| 00:00:01 | 
|* 1 | COUNT STOPKEY  |   |  |  |   |   | 
|* 2 | TABLE ACCESS FULL| TABLE_NAME |  1 | 112 |  2 (0)| 00:00:01 | 
--------------------------------------------------------------------------------- 

Le prestazioni possono essere migliorate mettendo un indice funzionale sulla lower(FIRST_NAME) ma che potrebbe essere utilizzato da entrambe le query esattamente stesso.

+1

+1 la risposta migliore IMO – kostja

2

vi consiglio caldamente, di utilizzare i parametri di query:

Query query = entityManager.createQuery("SELECT a FROM " 
    + Person.class.getSimpleName() 
    + " a WHERE lower(a.firstName) = :name"); 
query.setParameter("name", firstName); 
return query.getSingleResult(); 

Ciò ha due importanti motivi:

  • Si protegge contro SQL-injection
  • Consenti al server SQL di memorizzare nella cache la query analizzata migliorando le conseguenti esecuzioni delle prestazioni

Considerando

select * from (...) where rownum <= ? 

involucro: questo non costa niente sulle prestazioni. Puoi semplicemente ignorarlo.