Hibernate alternative dell'Unione
Domanda
Quali alternative devo implementare una query di unione usando l'ibernazione? So che l'ibernazione al momento non supporta le query sui sindacati, in questo momento l'unico modo in cui vedo per creare un sindacato è utilizzare una tabella di visualizzazione.
L'altra opzione è usare jdbc semplice, ma in questo modo perderei tutti i miei goodies di query di esempio / criterio, così come la convalida del mapping di ibernazione che hibernate esegue rispetto alle tabelle / colonne.
Soluzione
Usa VISUALIZZA. Le stesse classi possono essere mappate su diverse tabelle / viste usando il nome dell'entità, quindi non avrai nemmeno una duplicazione. Essere lì, fatto, funziona bene.
Il semplice JDBC ha un altro problema nascosto: non è a conoscenza della cache della sessione di Hibernate, quindi se qualcosa viene memorizzato nella cache fino alla fine della transazione e non cancellato dalla sessione di Hibernate, la query JDBC non la troverà. A volte potrebbe essere molto sconcertante.
Altri suggerimenti
Puoi usare id in (select id from ...) or id in (select id from ...)
es. anziché non funzionante
from Person p where p.name="Joe"
union
from Person p join p.children c where c.name="Joe"
potresti farlo
from Person p
where p.id in (select p1.id from Person p1 where p1.name="Joe")
or p.id in (select p2.id from Person p2 join p2.children c where c.name="Joe");
Almeno usando MySQL, però, ti imbatterai in problemi di prestazioni con esso in seguito. A volte è più facile invece fare un join di un uomo povero su due query:
// use set for uniqueness
Set<Person> people = new HashSet<Person>((List<Person>) query1.list());
people.addAll((List<Person>) query2.list());
return new ArrayList<Person>(people);
Spesso è meglio fare due semplici query piuttosto che una complessa.
EDIT:
per fare un esempio, ecco l'output EXPLAIN della query MySQL risultante dalla soluzione subselect:
mysql> explain
select p.* from PERSON p
where p.id in (select p1.id from PERSON p1 where p1.name = "Joe")
or p.id in (select p2.id from PERSON p2
join CHILDREN c on p2.id = c.parent where c.name="Joe") \G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: a
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 247554
Extra: Using where
*************************** 2. row ***************************
id: 3
select_type: DEPENDENT SUBQUERY
table: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
Extra: Impossible WHERE noticed after reading const tables
*************************** 3. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: a1
type: unique_subquery
possible_keys: PRIMARY,name,sortname
key: PRIMARY
key_len: 4
ref: func
rows: 1
Extra: Using where
3 rows in set (0.00 sec)
Soprattutto, 1. row non utilizza alcun indice e considera 200k + righe. Male! L'esecuzione di questa query ha richiesto 0,7 secondi in cui entrambe le sottoquery sono espresse in millisecondi.
Sono d'accordo con Vladimir. Anch'io ho esaminato l'uso di UNION in HQL e non sono riuscito a trovare un modo per aggirarlo. La cosa strana è che ho potuto trovare (nelle FAQ di Hibernate) che UNION non è supportato, segnalazioni di bug relative a UNION contrassegnate come "riparate", newsgroup di persone che affermano che le dichiarazioni verrebbero troncate a UNION e altri newsgroup di persone che segnalano che funziona bene ... Dopo una giornata passata inosservata, ho finito per riportare il mio HQL su SQL semplice, ma farlo in una vista nel database sarebbe una buona opzione. Nel mio caso, parti della query sono state generate dinamicamente, quindi ho dovuto creare l'SQL nel codice.
Ho una soluzione per uno scenario critico (per il quale ho lottato molto) con l'unione in HQL.
per es. Invece di non funzionare: -
select i , j from A a , (select i , j from B union select i , j from C) d where a.i = d.i
o
select i , j from A a JOIN (select i , j from B union select i , j from C) d on a.i = d.i
Potresti farlo in Hibernate HQL - >
Query q1 =session.createQuery(select i , j from A a JOIN B b on a.i = b.i)
List l1 = q1.list();
Query q2 = session.createQuery(select i , j from A a JOIN C b on a.i = b.i)
List l2 = q2.list();
allora puoi aggiungere entrambi gli elenchi - >
l1.addAll(l2);
Una vista è un approccio migliore, ma poiché hql in genere restituisce un elenco o un set ... puoi fare list_1.addAll (list_2). Fa assolutamente schifo rispetto a un sindacato ma dovrebbe funzionare.
Forse ho avuto un problema più diretto da risolvere. Il mio "per esempio" era in JPA con Hibernate come provider JPA.
Ho diviso le tre selezioni (due in un secondo caso) in selezioni multiple e ho combinato le raccolte restituite da me, sostituendo efficacemente un "unione tutto".
Anch'io ho sofferto di questo dolore - se la query viene generata in modo dinamico (ad esempio Criteri di ibernazione), non riuscirei a trovare un modo pratico per farlo.
La buona notizia per me era che stavo solo studiando l'unione per risolvere un problema di prestazioni quando usavo un 'o' in un database Oracle.
La soluzione che Patrick ha pubblicato (combinando i risultati in modo programmatico usando un set) mentre brutta (specialmente perché volevo fare anche il paging dei risultati) era adeguata per me.
Come ha detto Patrick, aggiungere gli ELENCO da ogni SELEZIONA sarebbe una buona idea, ma ricorda che si comporta come UNIONE TUTTO . Per evitare questo effetto collaterale, basta controllare se l'oggetto è già stato aggiunto nella raccolta finale o meno. Se no, quindi aggiungilo.
Qualcos'altro di cui dovresti preoccuparti è che se hai UNISCITI in ogni SELEZIONA , il risultato sarebbe un elenco di array di oggetti (
Spero che funzioni.
Ecco un caso speciale, ma potrebbe ispirarti a creare il tuo lavoro intorno. L'obiettivo qui è contare il numero totale di record da due diverse tabelle in cui i record soddisfano un determinato criterio. Credo che questa tecnica funzionerà in ogni caso in cui è necessario aggregare dati da più tabelle / fonti.
Ho un'impostazione speciale per le classi intermedie, quindi il codice che chiama la query con nome è breve e dolce, ma puoi usare qualsiasi metodo che usi normalmente insieme alle query con nome per eseguire la query.
QueryParms parms=new QueryParms();
parms.put("PROCDATE",PROCDATE);
Long pixelAll = ((SourceCount)Fetch.row("PIXEL_ALL",parms,logger)).getCOUNT();
Come puoi vedere qui, la query denominata inizia ad assomigliare molto a una dichiarazione sindacale:
@Entity
@NamedQueries({
@NamedQuery(
name ="PIXEL_ALL",
query = "" +
" SELECT new SourceCount(" +
" (select count(a) from PIXEL_LOG_CURR1 a " +
" where to_char(a.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
" )," +
" (select count(b) from PIXEL_LOG_CURR2 b" +
" where to_char(b.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
" )" +
") from Dual1" +
""
)
})
public class SourceCount {
@Id
private Long COUNT;
public SourceCount(Long COUNT1, Long COUNT2) {
this.COUNT = COUNT1+COUNT2;
}
public Long getCOUNT() {
return COUNT;
}
public void setCOUNT(Long COUNT) {
this.COUNT = COUNT;
}
}
Parte della magia qui è creare una tabella fittizia e inserire un record in essa. Nel mio caso, l'ho chiamato dual1 perché il mio database è Oracle, ma non credo sia importante come si chiama la tabella fittizia.
@Entity
@Table(name="DUAL1")
public class Dual1 {
@Id
Long ID;
}
Non dimenticare di inserire il tuo record fittizio:
SQL> insert into dual1 values (1);