Best practice per la codifica Java per riutilizzare parte di una query da contare
-
05-07-2019 - |
Domanda
Il Implement-result-paging-in- hibernate-getting-total-numero-di-righe fa scattare un'altra domanda per me, riguardo a alcuni problemi di implementazione :
Ora sai che devi riutilizzare parte della query HQL per fare il conteggio, come riutilizzare in modo efficiente?
Le differenze tra le due query HQL sono:
- la selezione è
count (?)
, anziché il pojo o la proprietà (o l'elenco di) - i recuperi non dovrebbero avvenire, quindi alcune tabelle non dovrebbero essere unite
- l'ordine
per
dovrebbe scomparire
Ci sono altre differenze?
Hai migliori pratiche di codifica per raggiungere questo riutilizzo in modo efficiente (preoccupazioni: impegno, chiarezza, prestazioni)?
Esempio per una semplice query HQL:
select a from A a join fetch a.b b where a.id=66 order by a.name
select count(a.id) from A a where a.id=66
AGGIORNAMENTO
Ho ricevuto risposte su:
- usando Criteri (ma usiamo principalmente HQL)
- manipolazione della query String (ma tutti concordano sul fatto che sembra complicato e non molto sicuro)
- racchiude la query , basandosi sull'ottimizzazione del database (ma si ha la sensazione che ciò non sia sicuro)
Speravo che qualcuno potesse dare opzioni lungo un altro percorso, più legato alla concatenazione di stringhe.
Potremmo creare entrambe le query HQL usando parti comuni ?
Soluzione
Bella domanda. Ecco cosa ho fatto in passato (molte cose che hai già menzionato):
- Controlla se è presente la clausola SELEZIONA .
- In caso contrario, aggiungi
select count (*)
- Altrimenti controlla se contiene DISTINCT o funzioni aggregate al suo interno. Se stai utilizzando ANTLR per analizzare la tua query, è possibile aggirare quelli ma è abbastanza coinvolto. Probabilmente stai meglio semplicemente racchiudendo il tutto con
select count (*) da ()
.
- In caso contrario, aggiungi
- Rimuovi
recupera tutte le proprietà
- Rimuovi
fetch
dai join se stai analizzando HQL come stringa. Se stai veramente analizzando la query con ANTLR puoi rimuovere completamenteleft join
; è piuttosto complicato controllare tutti i possibili riferimenti. - Rimuovi
ordina per
- A seconda di ciò che hai fatto in 1.2 dovrai rimuovere / modificare il
gruppo in base a
/avendo
.
Quanto sopra si applica a HQL, naturalmente. Per le domande di Criteria sei piuttosto limitato a ciò che puoi fare perché non si presta facilmente alla manipolazione. Se stai usando una sorta di layer wrapper in cima a Criteri, finirai con l'equivalente di un sottoinsieme (limitato) di risultati dell'analisi ANTLR e in questo caso potresti applicare la maggior parte di quanto sopra.
Dato che normalmente tieni premuto per compensare la tua pagina corrente e il conteggio totale, di solito eseguo prima la query effettiva con il limite / offset dato ed eseguo la query count (*)
solo se il numero di risultati restituiti è maggiore o uguale al limite E l'offset è zero (in tutti gli altri casi ho già eseguito il count (*)
o ho comunque restituito tutti i risultati). Si tratta ovviamente di un approccio ottimistico per quanto riguarda le modifiche simultanee.
Aggiorna (sull'assemblaggio manuale di HQL)
Non mi piace particolarmente questo approccio. Se mappato come query denominata, HQL presenta il vantaggio del controllo degli errori di build-time (beh, tecnicamente runtime, poiché SessionFactory deve essere costruito sebbene ciò avvenga comunque durante i test di integrazione). Quando viene generato durante l'esecuzione, non riesce durante l'esecuzione :-) Anche l'ottimizzazione delle prestazioni non è esattamente facile.
Lo stesso ragionamento si applica ai Criteri, ovviamente, ma è un po 'più difficile sbagliare a causa di API ben definite rispetto alla concatenazione di stringhe. La creazione di due query HQL in parallelo (una di paging e "conteggio globale") porta anche alla duplicazione del codice (e potenzialmente più bug) o ti costringe a scrivere un qualche tipo di layer wrapper per farlo per te. Entrambe le strade sono tutt'altro che ideali. E se è necessario farlo dal codice client (come nell'API superiore), il problema peggiora ancora.
In realtà ho riflettuto parecchio su questo problema. L'API di ricerca da Hibernate-Generic-DAO sembra un compromesso ragionevole; ci sono maggiori dettagli nella mia risposta alla domanda collegata sopra.
Altri suggerimenti
Hai provato a chiarire le tue intenzioni a Hibernate impostando una proiezione sui tuoi criteri (SQL?)? Ho usato principalmente i criteri, quindi non sono sicuro di quanto applicabile al tuo caso, ma ho usato
getSession().createCriteria(persistentClass).
setProjection(Projections.rowCount()).uniqueResult()
e lasciando che Hibernate riesca a capire da solo la cache / il riutilizzo / le cose intelligenti .. Non sei sicuro di quante cose intelligenti faccia effettivamente però .. A qualcuno interessa commentare questo?
Beh, non sono sicuro che questa sia una buona pratica, ma è la mia pratica :)
Se ho come query qualcosa del tipo:
select A.f1,A.f2,A.f3 from A, B where A.f2=B.f2 order by A.f1, B.f3
E voglio solo sapere quanti risultati otterrò, eseguo:
select count(*) from ( select A.f1, ... order by A.f1, B.f3 )
E quindi ottenere il risultato come intero, senza mappare i risultati in un POJO.
Analizzare la tua query per rimuovere alcune parti, come 'ordina per' è molto complicato. Un buon RDBMS ottimizzerà la tua query per te.
Buona domanda.
In una situazione HQL a mano libera userei qualcosa del genere ma questo non è riutilizzabile in quanto è abbastanza specifico per le entità date
Integer count = (Integer) session.createQuery("select count(*) from ....").uniqueResult();
Fallo una volta e regola il numero iniziale di conseguenza fino alla pagina.
Per i criteri, anche se utilizzo un campione come questo
final Criteria criteria = session.createCriteria(clazz);
List<Criterion> restrictions = factory.assemble(command.getFilter());
for (Criterion restriction : restrictions)
criteria.add(restriction);
criteria.add(Restrictions.conjunction());
if(this.projections != null)
criteria.setProjection(factory.loadProjections(this.projections));
criteria.addOrder(command.getDir().equals("ASC")?Order.asc(command.getSort()):Order.desc(command.getSort()));
ScrollableResults scrollable = criteria.scroll(ScrollMode.SCROLL_INSENSITIVE);
if(scrollable.last()){//returns true if there is a resultset
genericDTO.setTotalCount(scrollable.getRowNumber() + 1);
criteria.setFirstResult(command.getStart())
.setMaxResults(command.getLimit());
genericDTO.setLineItems(Collections.unmodifiableList(criteria.list()));
}
scrollable.close();
return genericDTO;
Ma questo vale ogni volta chiamando ScrollableResults: last ()
.