2008-10-14 9 views
45

Quelles sont les alternatives que j'ai pour implémenter une requête union en utilisant hibernate? Je sais qu'Hibernate ne prend pas en charge les requêtes syndicales pour le moment, la seule façon de créer une union est d'utiliser une table d'affichage.Hibernate Union alternatives

L'autre option consiste à utiliser jdbc simple, mais de cette façon je perdrais tous mes goodies de requêtes d'exemples/critères, ainsi que la validation de mappage d'hibernate effectuée par hibernate sur les tables/colonnes.

+2

L'une des solutions est d'utiliser l'entité globale, comme décrit [ici] (http://stackoverflow.com/questions/ 7322526/hibernate-jpa-is-it-possible-de-retrieve-heterogene-entities-in-a-single-que/7322738 # 7322738). –

Répondre

22

Utiliser VUE. Les mêmes classes peuvent être mappées à différentes tables/vues en utilisant le nom de l'entité, donc vous n'aurez même pas beaucoup de duplication. Être là, fait ça, ça marche.

Plain JDBC a un autre problème caché: il ne connaît pas le cache de session Hibernate, donc si quelque chose est mis en cache jusqu'à la fin de la transaction et non vidée de la session Hibernate, la requête JDBC ne le trouvera pas. Pourrait être très énigmatique parfois.

+2

Et encore plus curieux est quand vous avez un cache de second niveau en place, mais utilisez JDBC simple (par exemple pour le reporting) d'une autre partie de l'application. – javashlook

5

Je suis d'accord avec Vladimir. Moi aussi, j'ai cherché à utiliser UNION dans HQL et je n'ai pas trouvé de solution. La chose étrange était que je pouvais trouver (dans la FAQ d'Hibernate) que UNION n'est pas supporté, les rapports de bogues relatifs à UNION marqués 'fixed', les newsgroups de personnes disant que les déclarations seraient tronquées à UNION, et d'autres newsgroups bien ... Après une journée de travail avec elle, j'ai fini par reporter mon HQL sur 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, donc j'ai dû construire le SQL dans le code à la place.

+1

Merci d'avoir énuméré les différentes théories que vous avez trouvées, et celles qui n'étaient pas correctes. Les différences entre ce qu'un "développeur raisonnable" peut attendre, et ce qui diffère de cela, sont quelques-unes des notes les plus utiles. –

3

Une vue est une meilleure approche, mais puisque hql renvoie généralement une liste ou un ensemble ... vous pouvez faire list_1.addAll (list_2). Totalement nul par rapport à une union mais devrait fonctionner.

0

J'ai moi aussi traversé cette souffrance - si la requête est générée dynamiquement (par exemple, les critères d'Hibernation) alors je n'ai pas trouvé de façon pratique de le faire. La bonne nouvelle pour moi était que j'examinais seulement l'union pour résoudre un problème de performance en utilisant un 'ou' dans une base de données d'Oracle.

La solution que Patrick a postée (en combinant les résultats par programmation en utilisant un ensemble) tout en étant moche (d'autant plus que je voulais faire des résultats de pagination aussi) me convenait.

2

Peut-être que j'avais un problème plus simple à résoudre. Mon 'par exemple' était en JPA avec Hibernate en tant que fournisseur JPA.

Je divise les trois sélections (deux dans un deuxième cas) en sélection multiple et combine les collections retournées moi-même, en remplaçant effectivement un «union tout».

+2

Cela fonctionne, mais c'est moins efficace. –

54

Vous pouvez utiliser id in (select id from ...) or id in (select id from ...)

par exemple au lieu de non-travail

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 en utilisant MySQL, vous aurez des problèmes de performance avec elle plus tard, cependant. Il est parfois plus facile de faire un pauvre homme de se joindre à deux requêtes au lieu:

// 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 d'un complexe.

EDIT:

pour donner un exemple, voici la sortie EXPLIQUEZ de la requête MySQL résultant de la solution 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) 

Plus important encore, 1. ligne n'utilise pas tout indice et considère 200k + lignes. Mal! L'exécution de cette requête a pris 0.7s où les deux sous-requêtes sont en millisecondes.

4

J'ai une solution pour un scénario critique (pour lequel j'ai beaucoup lutté) avec 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 

OU

select i , j from A a JOIN (select i , j from B union select i , j from C) d on a.i = d.i 

que 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 u peut ajouter à la fois la liste ->

l1.addAll(l2); 
+1

Le syndicat sera en cours, pas fait par la base de données, c'est la même chose que la suggestion de Walt. –

+0

donc, et si vous voulez trier ici? vous pourriez finir par dupliquer le mécanisme de tri en implémentant un autre algorithme de fusion. très pas gentil – Bondax

0



Comme Patrick dit, la LISTE annexant s de chaque SELECT serait une bonne idée, mais rappelez-vous qu'il agit comme UNION ALL. Pour éviter cet effet secondaire, contrôlez simplement si l'objet est déjà ajouté dans la collection finale ou non. Si non, alors ajoutez-le.
Quelque chose d'autre que vous devriez prendre en compte est que si vous avez une REJOIGNEZ dans chaque SELECT, le résultat serait une liste de tableau d'objets (List<Objetc[]>) donc vous devez parcourir pour ne garder que l'objet ce dont tu as besoin

Espérons que cela fonctionne.

0

Voici un cas particulier, mais qui pourrait vous inspirer pour créer votre propre travail. Le but ici est de compter le nombre total d'enregistrements de deux tables différentes où 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 une configuration de classes intermédiaires spéciales, donc le code qui appelle la requête nommée est court et doux, mais vous pouvez utiliser n'importe quelle méthode que vous utilisez normalement en conjonction 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 à regarder un lot aweful comme une déclaration syndicale:

@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 est ici de créer une table factice et insérez un enregistrement dans il. Dans mon cas, je l'ai appelé dual1 parce que ma base de données est Oracle, mais je ne pense pas que ce soit ce que vous appelez la table fictive.

@Entity 
@Table(name="DUAL1") 
public class Dual1 { 
    @Id 
    Long ID; 
} 

Ne pas oublier d'insérer votre dossier factice:

SQL> insert into dual1 values (1);