Quelles sont les alternatives pour implémenter une requête d'union avec hibernate? Je sais que hibernate ne prend pas en charge les requêtes des syndicats pour le moment. Pour le moment, la seule façon de créer un syndicat consiste à utiliser un tableau d'affichage.
L'autre option est d'utiliser plain jdbc, mais de cette façon, je perdrais tous mes goodies de requête exemple/critère, ainsi que la validation du mappage hibernate effectuée par hibernate par rapport aux tables/colonnes.
Utilisez VIEW. Les mêmes classes peuvent être mappées vers différentes tables/vues à l'aide du nom de l'entité, de sorte que vous n'aurez même pas beaucoup de duplication. Être là, ça fait, ça marche.
JDBC simple a un autre problème caché: il n'a pas connaissance du cache de session Hibernate. Par conséquent, si un élément est mis en cache jusqu'à la fin de la transaction et n'est pas vidé de la session Hibernate, la requête JDBC ne le trouvera pas. Pourrait être très déroutant parfois.
Vous pouvez utiliser id in (select id from ...) or id in (select id from ...)
par exemple. au lieu de inactif
from Person p where p.name="Joe"
union
from Person p join p.children c where c.name="Joe"
vous pourriez faire
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");
Au moins, si vous utilisez MySQL, vous rencontrerez des problèmes de performances plus tard. Il est parfois plus facile de joindre un pauvre homme à deux requêtes:
// 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);
Il est souvent préférable de faire deux requêtes simples qu'un complexe.
MODIFIER:
pour donner un exemple, voici la sortie EXPLAIN de la requête MySQL obtenue à partir de la solution de sous-sélection:
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)
Plus important encore, 1. row n'utilise pas d'index et prend en compte 200k + lignes. Mal! L'exécution de cette requête a pris 0,7 seconde alors que les deux sous-requêtes sont en millisecondes.
Je suis d'accord avec Vladimir. Moi aussi, je me suis penché sur l’utilisation de UNION dans HQL et je n’ai trouvé aucun moyen de le contourner. Ce qui est étrange, c’est que j’ai trouvé (dans la FAQ d’Hibernate) que UNION n’est pas prise en charge, que les rapports de bogues relatifs à UNION sont marqués «fixes», que des groupes de discussion indiquent que les déclarations seront tronquées sous UNION, bien .... Après une journée de travail, j’ai finalement reporté mon portage HQL vers SQL, mais le faire dans une vue dans la base de données serait une bonne option. Dans mon cas, des parties de la requête ont été générées dynamiquement. J'ai donc dû créer le code SQL dans le code.
J'ai une solution pour un scénario critique (pour lequel j'ai beaucoup lutté) avec l'union dans HQL.
par exemple. Au lieu de ne pas travailler: -
select i , j from A a , (select i , j from B union select i , j from C) d where a.i = d.i
OR
select i , j from A a JOIN (select i , j from B union select i , j from C) d on a.i = d.i
VOUS pourriez faire dans 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();
alors vous pouvez ajouter les deux liste ->
l1.addAll(l2);
Une vue est une meilleure approche, mais comme hql renvoie généralement une liste ou un ensemble ... vous pouvez faire list_1.addAll (list_2). Totalement nul par rapport à un syndicat mais devrait fonctionner.
J'ai peut-être un problème plus simple à résoudre. Mon "par exemple" était dans JPA avec Hibernate en tant que fournisseur JPA.
J'ai divisé les trois sélections (deux dans un deuxième cas) en sélections multiples et combiné les collections renvoyées moi-même, remplaçant ainsi un «syndicat tous».
J'ai moi aussi traversé cette épreuve - si la requête est générée de manière dynamique (par exemple, Critères d'Hibernate), je ne pourrais pas trouver un moyen pratique de le faire.
La bonne nouvelle pour moi est que je n’enquêtais que sur l’union pour résoudre un problème de performances lors de l’utilisation d’un "ou" dans une base de données Oracle.
La solution proposée par Patrick (combiner les résultats par programmation à l’aide d’un ensemble) alors que moche (surtout que je voulais aussi faire une pagination des résultats) me convenait parfaitement.
Voici un cas particulier, mais qui pourrait vous inciter à créer votre propre travail. L'objectif ici est de compter le nombre total d'enregistrements de deux tables différentes lorsque les enregistrements répondent à un critère particulier. Je crois que cette technique fonctionnera dans tous les cas où vous devez regrouper des données provenant de plusieurs tables/sources.
J'ai quelques classes spéciales intermédiaires configurées, donc le code qui appelle la requête nommée est court et simple, mais vous pouvez utiliser la méthode que vous utilisez normalement avec des requêtes nommées pour exécuter votre requête.
QueryParms parms=new QueryParms();
parms.put("PROCDATE",PROCDATE);
Long pixelAll = ((SourceCount)Fetch.row("PIXEL_ALL",parms,logger)).getCOUNT();
Comme vous pouvez le voir ici, la requête nommée commence à ressembler énormément à une déclaration d'union:
@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;
}
}
Une partie de la magie ici consiste à créer une table factice et à y insérer un enregistrement. Dans mon cas, je l'ai nommé dual1 parce que ma base de données est Oracle, mais je ne pense pas que le nom de la table factice importe peu.
@Entity
@Table(name="DUAL1")
public class Dual1 {
@Id
Long ID;
}
N'oubliez pas d'insérer votre fiche factice:
SQL> insert into dual1 values (1);
Comme Patrick l'a dit, ajouter laLISTEs de chaqueSELECTserait une bonne idée, mais rappelez-vous qu'il s'agit de UNION ALL . Pour éviter cet effet secondaire, il suffit de contrôler si l'objet est déjà ajouté à la collection finale ou non. Si non, alors ajoutez-le .
Une autre chose à laquelle vous devriez vous intéresser est que si vous avez unJOINdans chaqueS&EACUTE;LECTIONNEZ, le résultat sera une liste de tableaux d’objets (List<Objetc[]>
), il vous faudra donc parcourez-le pour ne garder que l'objet dont vous avez besoin .
Esperons que ça marche.