-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 18 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: problème avec fetch
PostPosted: Tue Feb 21, 2006 12:07 pm 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Bonjour,

Supposons une table CAT :
CAT
id nom
--------------------
1 Felix
2 Garfield

Et une table Kitten :
KITTEN
id nom parent_id
---------------------------------------
1 chat1 1
2 chat2 1
3 chat3 2
4 chat4 2

Pour éviter d'avoir des sous requêtes SQL, voila le HQL qui me retourne l'ensemble de Kitten et de Cat en une seul fois :
select cat
from cat as Cat
left join fetch cat.kitten as kitten

Le SQL généré va être du style :
select cat.id,
cat.nom,
kitten.id,
kitten.nom
from cat as Cat
left outer join kitten kitten on kitten.parent_id=cat.id

Cette requête SQL me retourne logiquement 4 enregistrements.

Malheureusement, la taille de la liste d'objets retourné est également de 4, alors que je m'attendais à ce qu'il n'y ai que 2 objets Cat retournés sur lesquelles j'itèrerais la collection de Kitten.

Y a t'il un moyen pour qu'il n'y ai que 2 objets retournés contenant chacun la bonne itération?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 22, 2006 9:04 am 
Senior
Senior

Joined: Tue May 10, 2005 9:00 am
Posts: 125
mapping et classes associée s'il te plait :)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 22, 2006 11:05 am 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Salut tchize,

Je génére avec middlegen (super outils!!!!). Ci dessous les classes + HBM.

L'exemple que je donne est une simplification. Dans la pratique, je travaille sur une base de pres de 100 tables et je fais des fetch un peu partout pour réduire le nombre de requetes mais ce problème d'itération me complique grandement la tache.

Code:
public class Cat implements Serializable {

    /** identifier field */
    private Integer id;

    /** nullable persistent field */
    private String nom;

    /** persistent field */
    private Set kittens;

    /** full constructor */
    public Cat(Integer id, String nom, Set kittens) {
        this.id = id;
        this.nom = nom;
        this.kittens = kittens;
    }

    /** default constructor */
    public Cat() {
    }

    /** minimal constructor */
    public Cat(Integer id, Set kittens) {
        this.id = id;
        this.kittens = kittens;
    }

    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNom() {
        return this.nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public Set getKittens() {
        return this.kittens;
    }

    public void setKittens(Set kittens) {
        this.kittens = kittens;
    }

    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }

    public boolean equals(Object other) {
        if ( !(other instanceof Cat) ) return false;
        Cat castOther = (Cat) other;
        return new EqualsBuilder()
            .append(this.getId(), castOther.getId())
            .isEquals();
    }

    public int hashCode() {
        return new HashCodeBuilder()
            .append(getId())
            .toHashCode();
    }

}



Code:
public class Kitten implements Serializable {

    /** identifier field */
    private int id;

    /** identifier field */
    private String nom;

    /** persistent field */
    private test.Cat cat;

    /** full constructor */
    public Kitten(int id, String nom, test.Cat cat) {
        this.id = id;
        this.nom = nom;
        this.cat = cat;
    }

    /** default constructor */
    public Kitten() {}

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNom() {
        return this.nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public test.Cat getCat() {
        return this.cat;
    }

    public void setCat(test.Cat cat) {
        this.cat = cat;
    }

    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .append("nom", getNom())
            .toString();
    }

    public boolean equals(Object other) {
        if ( !(other instanceof Kitten) ) return false;
        Kitten castOther = (Kitten) other;
        return new EqualsBuilder()
            .append(this.getId(), castOther.getId())
            .append(this.getNom(), castOther.getNom())
            .isEquals();
    }

    public int hashCode() {
        return new HashCodeBuilder()
            .append(getId())
            .append(getNom())
            .toHashCode();
    }

}



et le fichier de mapping :

Code:
<class
    name="test.Cat"
    table="test.cat"
>
    <id
        name="id"
        type="int"
        column="id"
    >
        <generator class="assigned" />
    </id>
    <property
        name="nom"
        type="java.lang.String"
        column="nom"
    />
    <!-- associations -->
    <!-- bi-directional one-to-many association to Kitten -->
    <set
        name="kittens"
        lazy="true"
        inverse="true"
    >
        <key>
            <column name="parent_id" />
        </key>
        <one-to-many
            class="test.Kitten"
        />
    </set>
</class>
<class
    name="test.Kitten"
    table="test.kitten"
>
    <composite-id>
        <key-property
            name="id"
            column="id"
            type="int"
            length="4"
        />
        <key-property
            name="nom"
            column="nom"
            type="java.lang.String"
        />
    </composite-id>   


    <!-- associations -->
    <!-- bi-directional many-to-one association to Cat -->
    <many-to-one
        name="cat"
        class="test.Cat"
        not-null="true"
    >
        <column name="parent_id" />
    </many-to-one>
</class>


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 22, 2006 11:56 am 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
J'ai bien 4 objets Cat retournés alors que je n'en attendais que 2 mais les itérations de Kitten dans chaque objet Cat sont bonnes (j'ai bien 2 kittens pour chaque Cat).

Il n'y a donc plus vraiment de problème.

C'est tout de même étrange que Hibernate retourne le même nombre d'objets qu'il y a de row retournés par la requête SQL sans vérifier que les objet retournés sont identiques.

Je vais donc filtrer moi même la liste retournée pour enlever les objet en double, triple... (c'est moins pire que ce que je pensais)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 4:32 am 
Senior
Senior

Joined: Tue May 10, 2005 9:00 am
Posts: 125
Si c'est uniquement les objet en double de type 'Cat' qui te dérangent, ajoute
'group by Cat.id' à la requete HQL.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 5:55 am 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
une requête de sélection avec GROUP BY sans notion d'agrégation.... ca marche ca?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 6:00 am 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Pour info, j'initialise un HashSet d'après la List, ca permet de garantir l'unicité des objets même si je trouve que c'est pas super propre de faire ca.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 6:43 am 
Senior
Senior

Joined: Tue May 10, 2005 9:00 am
Posts: 125
sebastienX wrote:
une requête de sélection avec GROUP BY sans notion d'agrégation.... ca marche ca?

Pas nécessaire vu que Hibernate ne récupère que les ID :)
(voir 14.10 The group by clause dans doc hibernate 3, y a un exemple de group by sans aggregation)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 10:23 am 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Merci pour l'info, cependant sur les exemple donnés il y a bien une fonction d'agrégation sur chaque exemple....


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 12:03 pm 
Senior
Senior

Joined: Tue May 10, 2005 9:00 am
Posts: 125
sebastienX wrote:
Merci pour l'info, cependant sur les exemple donnés il y a bien une fonction d'agrégation sur chaque exemple....


Dernier exemple

select cat from Cat cat join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by coun(kitten) asc, sume(kitten.weight) desc


pas d'aggregation dans les champs select (et on peut virer sans prob le having et le order by)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 23, 2006 1:52 pm 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Effectivement, merci pour tes informations,

Je reviens tout de même sur mon problème initiale : éviter d'avoir un nombre important de requêtes en utilisant des fetch.
L'exemple Cat, Kitten donné en exemple était volontairement simpliste. En vérité, je travaille sur près d'une centaine de tables d'une BD fortement normalisée avec de nombreuses jointures. Les requêtes HQL que je créé contiennent donc plusieurs clauses "left join fetch" (jusqu'à 20 clauses de ce type dans la même requête HQL) qui me permettent d'aller assez loin dans la granulosité des objets que je veux récupérer.

Le SQL généré par Hibernate est très bon (ça fait des requêtes SQL très longues mais tous les résultats recherchés sont bien récupérés) . Cependant, je ne peux pas "naviguer" très loin dans les objets récupérés (seulement un niveau d'itération) : je peux par exemple faire cat.getKitten() qui me retournera bien la collection de Kitten mais en supposant que la table KITTEN elle même soit lié à une table LITTLEKITTEN :
Code:
<class
    name="test.Kitten"
    table="test.kitten"
>
   <id
        name="id"
        type="int"
        column="id"
    >
        <generator class="assigned" />
    </id>

    <property
        name="nom"
        type="java.lang.String"
        column="nom"
    />

    <many-to-one
        name="cat"
        class="test.Cat"
        not-null="true"
    >
        <column name="parent_id" />
    </many-to-one>
    <set
        name="littlekittens"
        lazy="true"
        inverse="true"
    >
        <key>
            <column name="kitten_r" />
        </key>
        <one-to-many
            class="test.Littlekitten"
        />
    </set>
</class>


Et bien dans ce cas, si j'itère sur littlekittens, je me retrouve avec une erreur "Failed to lazily initialize a collection" mais la requete SQL générée rapporte pourtant bien tous les "littlekittens". Apparemment, Hibernate ne retourne qu'un seul niveau d'itération des objets et tronque donc une partie des informations retournés par le SQL.

Dans la doc (11.3), ils disent :
Quote:
Notez que, dans l'implémentation courante, seule une seule collection peut être "fetchée" par requête (une autre stratégie ne serait pas performante)...


Ca limite grandement l'intérêt d'Hibernate dans le cas d'une BD fortement normalisée car cela suppose qu'hibernate fasse une requête SQL par jointure... et donc les performances en sont diminuées....
A moins qu'il n'y ai une solution à ce problème....


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 24, 2006 5:19 am 
Senior
Senior

Joined: Tue May 10, 2005 9:00 am
Posts: 125
Je ne suis pas sur de te suivre, mais je vais essayer de répondre :)

Quand tu fait un select en hql, ca te retourne des objects, mais ces objets ne sont généralement pas rempli (si tu active le lazy initialisation) ou sont completement remplis (lazy initialization activé).

Si j'ai bien compris, tu voudrais faire un hql qui remplisse en partie Cat, et les Kitten associé à partir des données recues. Cependant, quoi qu'il arrive, Hibernate ne retournera jamais un 'demi-objet'. C'est à dire, pas question de demander à Hibernate de retourner de objets Cat avec une liste de Kitten, mais en appliquant une requete telle que seuls les Kitten lourds apparaissent dans getKitten() pour les objets Cat (alors que le mapping aurait stipulé tous les Kittens). Pourquoi? Tout simplement pour éviter des incohérences lors de la manipulation ultérieure de l'objet.

Maintenant, tu essaie probablement de construire une requête HQL qui fasse tous les left join nécessaires pour que CAT et les objets liés contiennent les données nécessaire. Cependant, déterminer à partir d'une requete HQL qu'il y aura tout et, je gage, assez difficile à déterminer. De plus en essayant de faire une requete HQL de ce genre, tu tente de contourner le principe du 'laisse Hibernate gérer tes objets'. Contente toi de récupérer les ID cat, le reste sera chargé à la demande, c'est performant et çà évite de charger trop de données inutiles (ou de charger deux fois des données déjà présentes!).

Je suis pas sûr d'avoir aidé là mais bon :)

Pour ce qui est de ton exception, il faudrait le HQL et le stacktrace, au minimum.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 24, 2006 6:49 am 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Si si, tu as bien compris mon problème : remplir toutes mes collections d'objets et leurs itérations respectives d'objets via une seule requête HQL.

Le SQL généré est correct est me permet bien de récupérer toute la "profondeur" des données de mon arborescence de table, c'est l'initialisation des objets POJO qui n'est pas effectuée.

Pour garder l'indépendance des couches du projet sur lequel je travaille, je suis obligé de détacher mes objets de la session, raison pour laquelle les objets que je retourne ne peuvent utiliser le chargement tardif.

J'ai 2 contraintes : d'une part la couche Struts doit travailler avec des objets déconnectés qui doivent donc être correctement initiliasés ainsi que l'arborescence des collections d'objets liés. (si j'ai une méthode qui retourne un Cat, je veux pouvoir itérer sur Kitten et pouvoir également itérer sur la collection Littlekitten des objets Kitten)
D'autre part, le nombre de requêtes sql générées doit être le plus faible possible, or Hibernate génèrera autant de requetes SQL qu'il y a de jointures : si j'ai une "profondeur" de 5 jointures pour récupèrer les données souhaités entre par exemple
CAT <-- KITTEN <-- LITTLEKITTEN <-- OWNER <-- ADRESS
j'aurais 5 requetes Hibernate malgres tous les "left join fetch" que j'aurais beau mettre dans ma requête HQL.
Alors que si je fais du pur JDBC, j'ai la garantie de pouvoir retourner toutes les données que je souhaite en une seule requête SQL.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 24, 2006 7:21 am 
Senior
Senior

Joined: Tue May 10, 2005 9:00 am
Posts: 125
sebastienX wrote:

J'ai 2 contraintes : d'une part la couche Struts doit travailler avec des objets déconnectés qui doivent donc être correctement initiliasés ainsi que l'arborescence des collections d'objets liés. (si j'ai une méthode qui retourne un Cat, je veux pouvoir itérer sur Kitten et pouvoir également itérer sur la collection Littlekitten des objets Kitten)

1) rien n'oblige struts a utiliser les objets déconnecté (ici on se contente de mettre un requestfilter au dessus de la requete qui se charge de déconnecter la session à la fin de la requete si c'est nécessaire.)
2) Tu peux toujours désactiver le lazy initialisation (attention tu risque de charger toutes ta base de données d'un coup!, note que mettre 20 left join court le meme risque)

Quote:
D'autre part, le nombre de requêtes sql générées doit être le plus faible possible, or Hibernate génèrera autant de requetes SQL qu'il y a de jointures : si j'ai une "profondeur" de 5 jointures pour récupèrer les données souhaités entre par exemple
CAT <-- KITTEN <-- LITTLEKITTEN <-- OWNER <-- ADRESS
j'aurais 5 requetes Hibernate malgres tous les "left join fetch" que j'aurais beau mettre dans ma requête HQL.

Cette contrainte de projet est douteuse, d'autant que qu'une tartine de join ou left cause un gros problème sur la quantité de données recue (exemple, le parent X, tous ses enfants et les cours des enfants, çà ne fait que 3 niveau de collection, les données du parent seront présente dans chaque row. Si le parent à 3 enfant et que chaque enfant a 10 cours, les datas du parent apparaitront 30 fois dans le resultat)
Quote:
Alors que si je fais du pur JDBC, j'ai la garantie de pouvoir retourner toutes les données que je souhaite en une seule requête SQL.

Toutes les données et plus si affinité. Si tu parcours les docs en ligne, il est fait mention quelque part, je sais plus où, de la comparaison entre utiliser Hibernate ou faire ses requetes optimisée via DAO.

Si tu veux vraiment récupérer ta hierarchie de collection en une seule requete SQL, je crains qu'il te faille utiliser autre chose qu'Hibernate...


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 24, 2006 7:11 pm 
Newbie

Joined: Tue Jan 17, 2006 6:09 am
Posts: 15
Quote:
Cette contrainte de projet est douteuse, d'autant qu'une tartine de join ou left cause un gros problème sur la quantité de données recue (exemple, le parent X, tous ses enfants et les cours des enfants, çà ne fait que 3 niveau de collection, les données du parent seront présente dans chaque row. Si le parent à 3 enfant et que chaque enfant a 10 cours, les datas du parent apparaitront 30 fois dans le resultat)


Tu estimes donc qu'il est plus judicieux d'avoir un grand nombre de requêtes SQL (ce que fait le chargement tardif) retournant une petite quantité de données plutôt que d'avoir une seule requête SQL contenant plusieurs jointures et retournant un grand nombre de données.
Je ne suis pas tout à fait d'accord sur ce point. Tout d'abord, cela "pompe" sur la base de données et la multiplication des requêtes pourra faire exploser plus rapidement le pool de connexion. Ensuite, le chargement tardif des objets a l'inconvénient de multiplier les allers retours entre la couche applicative java et la couche data de la BD et cela me semble crade dans le principe alors qu'une requête SQL bien faite en JDBC pur permet de déléguer entièrement le traitement des données à la BD dont c'est le rôle après tout.

Comme tu le soulignais, il y a effectivement la désactivation du lazy initialisation qui permet de charger toute la grappe de données mais je n'ai pas toujours besoin de toute cette grappe de données lorsque j'accède à une table. Je perdrais en performance d'un coté ce que je gagne de l'autre...


La notion de 'demi-objet' que tu évoquais précédemment est aussi très contraignante :
Supposons cette grappe d'objets :

Code:
cat1
|----kitten1
''''''''''''''''''''|--------littlekitten1
''''''''''''''''''''|--------littlekitten2
|----kitten2
''''''''''''''''''''|--------littlekitten3
''''''''''''''''''''|--------littlekitten4


Si je veux une requête qui retourne l'élément Cat dont le Littlekitten est 'littlekitten1', je fais en HQL :
select cat from Cat as cat
left join fetch cat.kittens as kittens
left join fetch kittens.littlekittens as littlekitten
where littlekitten.nom='littlekitten1'
Le SQL généré me retourne une seule ligne (concaténation de cat1, little1 et littlekitten1) mais si j'itère sur les champs de l'objet cat1 retourné j'obtient kitten1 et kitten2 littlekitten1, littlekitten2, littlekitten3 et littlekitten4 alors que j'attendais seulement kitten1 et littlekitten1.
Donc ça aussi ça limite l'utilisation des fetch. Je trouve dommage de faire un langage de requétage aussi élaboré que le HQL si la représentation objet ne permet pas de filtrer les collections autrement qu'en faisant session.filter(...)

Je vais tenter de faire la recherche que je souhaite en SQL natif via session.createSQLQuery(...) en espérant que cela résoudra le problème d'initialisation.
Et si ça ne me convient toujours pas, je passerais à JDBC pour cette requête spéciale.

Merci pour tes précieux conseils :-)


Last edited by sebastienX on Sat Feb 25, 2006 1:37 pm, edited 1 time in total.

Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.