Question

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 de visualisation.

L’autre option consiste à utiliser plain jdbc, mais de cette manière, je perdrais tous les goodies de ma requête exemple / critère, ainsi que la validation du mappage hibernate effectuée par hibernate par rapport aux tables / colonnes.

Était-ce utile?

La solution

Utilisez VIEW. Les mêmes classes peuvent être mappées sur différentes tables / vues à l'aide du nom d'entité, de sorte que vous n'aurez même pas beaucoup de duplication. Être là, ça fait, ça marche.

Plain JDBC a un autre problème caché: il ne connaît pas le 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. Cela peut parfois être très déroutant.

Autres conseils

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

par exemple. au lieu de inactifs

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 faire la jointure d'un homme pauvre sur deux requêtes à la place:

// 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 plutôt qu'une requête complexe.

EDIT:

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 «réparés», que des groupes de discussion indiquent que les déclarations seraient tronquées sous UNION et que d’autres groupes de discussion signalant que cela fonctionne. bien... Après une journée de travail, j’ai finalement reporté le portage de mon HQL en SQL simple, mais le faire dans une vue de 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 en HQL.

p. ex. 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 

Vous pouvez 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 listes - >

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 "union all".

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 trouverais pas de 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 performance 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 qu’elle était moche (surtout que je voulais aussi faire de la pagination des résultats) me convenait.



Comme Patrick l'a dit, l'ajout des LIST de chaque SELECT serait 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 devez vous intéresser est que si vous avez un JOIN dans chaque SELECT , le résultat sera une liste de tableaux d'objets ( Répertoriez < Objetc [] > ) de sorte que vous deviez effectuer une itération dessus pour ne conserver que l'objet dont vous avez besoin.

J'espère que ça marche.

Voici un cas spécial, mais qui pourrait vous inspirer pour 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 pense que cette technique fonctionnera dans tous les cas où vous devez regrouper des données provenant de plusieurs tables / sources.

La configuration de certaines classes intermédiaires est particulière. 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 beaucoup à 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ée dual1 parce que ma base de données est Oracle, mais je ne pense pas que ce que vous appelez la table factice ait une importance

@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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top