Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Hibernate Java Discussion :

Charger un graphe d'objets


Sujet :

Hibernate Java

  1. #1
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut Charger un graphe d'objets
    Bonjour,
    Mon problème est le suivant:

    J'ai un modèle objet avec une vingtaine de classes, où les OneToMany sont en LAZY.
    Mais Parfois on doit envoyer au client lourd la totalité des objets liés à un point donné du graphe; j'ai codé un bean qui par introspection va appeler size() sur toutes les collections, pour forcer le chargement des OneToMany.

    Mais c'est un peu lent, et quelqu'un m'a suggérer de tout récupérer en une fois avec une grosse requête qui fait des join fetch, la doc Hibernate a l'air de dire que ca pourrait bien forcer le chargement.

    Probleme:
    -Est-il possible de coder une telle requête, et les perfs ne seront-elles pas encore pires?
    -J'ai essayé de tester avec seulement 2 entités du graphe d'objet, avec une requete:
    "select mere from Mere mere "
    + "left join fetch mere.filles fille";
    Car on veut récupérer toutes les mères meme celles qui n'ont pas de filles.

    Probleme, quand du cote client je recupere les Meres, il y en a bien le bon nombre, mais chacune d'entre elles a, en plus de ses filles "réelles" (celles qui sont en BD) des filles "fantômes" qui sont NULL!
    Plus précisément, les mères sans filles n'ont pas de fille fantôme, mais la première mère "avec fille" de la liste a une fille NULL, la seconde 2, etc..

    Bizarre non?
    Je suis sûr qu'il y a une expliocation simple, ce n'est pas par hasard qu'on a une progression arithmetique 1,2,3, etc.. filles fantomes.

  2. #2
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    Pour préciser le contexte je bosse sur JBoss avec des EJB3/Hibernate, et le client est une IHM Swing avec de la cartographie. C'est pour ça qu'on a besoin de charger un gros modèle d'un coup, pour que les interactions avec la couche de gestion des tracés sur carto n'aient ensuite pas constamment des temps d'attente à cause d'interactions intempestives avec le serveur J2EE. Je répond préventivement oui, dans notre cas on a besoin de charger tout ça d'un coup.

    Je commence à penser que notre problème est technologique, on se heurte au fait que pour l'instant la gestion du LAZY ne se fait que dans une session Hibernate. Et ce qu'on voudrait c'est une façon simple et performante de surcharger ce comportement dans certains cas d'utilisations bien précis, pas seulement pour le chargement d'UNE collection mais pour charger un graphe d'objets représentant un modèle.

    Finalement je me demande si la solution la plus élégante ne serait pas, en l'état actuel de la technologie de mapping O/R, d'avoir , pour chaque classe du graphe de modèle, une classe surchargeant les getters OneToMany en EAGER (dans les Entity de base qui sont notre modèle, les OneToMany sont tous en LAZY), peut-être avec un type générique de wrapping pour être plus propre.
    Par contre concrètement je ne vois pas trop comment mettre ça en oeuvre, en ce qui concerne la problématique de surcharger les Entity sans que la classe fille ne soit persistée en BD (il me faudrait un genre de @MappedSuperclass mais dans l'autre sens de la relation d'héritage, la surcharge n'étant utile que lors du chargement du modèle complet à partir d'un point d'entrée, dans certains cas d'utilisation).
    Je me demande même si on peut surcharger des annotations comme ça.

    Toute aide est la bienvenue car malgré nos efforts on est à la bourre et le flot de specs de nouvelles fonctionnalités ne montre aucun signe de tarissement

  3. #3
    Membre confirmé
    Avatar de grishka
    Inscrit en
    janvier 2003
    Messages
    285
    Détails du profil
    Informations forums :
    Inscription : janvier 2003
    Messages : 285
    Points : 465
    Points
    465
    Par défaut
    Salut,

    le "join fetch" est performant si on limite à une seule relation 1->N par objet père (à cause du produit cartésien engendré dans le résultat de la requête), ce qui n'est pas ton cas.

    je laisserais le paramètrage en lazy pour l'exploration du graphe via les getter. Avec ta solution d'exploration, tu refais "en dur" ce que Hibernate te permet via le paramétrage (et généralement il ne vaut mieux pas figer le mode de récupération dans le paramétrage, c'est un nid à emmerde). De plus tu te retrouve avec des N+1 select en cascade (et donc n*m*o requêtes suivant la profondeur du graphe), ce qui est difficilement acceptable pour une requête de reporting.

    Je te conseille de composer des requêtes de reporting en HQL en évitant les fetch join et en utilisant les batchs (tu te retrouves donc avec (n/t)*(m/t)*(o/t) *... requêtes, t étant la taille des batchs, les divisions étant arrondies à l'entier supérieur).

    de plus on peut parfois limiter les N+1 selects avec une approche horizontale plutot que verticale du graphe d'objet (exploration en largeur plutot qu'en profondeur) : tu utilises l'opérateur "IN" , puis tu effectues la "jointure" (repeuplement de la hiérarchie) en java.
    "Les gens normaux croient que si ca marche, c'est qu'il n'y a rien à reparer. Les ingénieurs croient que si ca marche, c'est que ca ne fait pas encore assez de choses."
    --Scott Adams

  4. #4
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    Merci de ta réponse mais je n'ai pas compris la solution alternative avec ce que tu appelles les "batchs". Tu pourrais me diriger vers un lien qui expliquerait un peu le principe parce que la ca me passe un peu au dessus de la tete

  5. #5
    Membre du Club
    Inscrit en
    janvier 2007
    Messages
    100
    Détails du profil
    Informations personnelles :
    Âge : 35

    Informations forums :
    Inscription : janvier 2007
    Messages : 100
    Points : 57
    Points
    57
    Par défaut
    Bon j'avoue pas avoir tout compris à ton problème, mais est-ce que tu ne pourrais pas te débrouiller avec la méthode Hibernate.initialize(o) (qui force le chargement d'un proxy) ?

  6. #6
    Membre confirmé
    Avatar de grishka
    Inscrit en
    janvier 2003
    Messages
    285
    Détails du profil
    Informations forums :
    Inscription : janvier 2003
    Messages : 285
    Points : 465
    Points
    465
    "Les gens normaux croient que si ca marche, c'est qu'il n'y a rien à reparer. Les ingénieurs croient que si ca marche, c'est que ca ne fait pas encore assez de choses."
    --Scott Adams

  7. #7
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    Tiens je n'avais pas fait attention à cette partie de la doc Hibernate. Cette phrase semble bien correspondre à mon cas:
    "Le chargement par lot de collections est particulièrement utile si vous avez des arborescenses récursives d'éléments (typiquement, le schéma facture de matériels). (Bien qu'un sous ensemble ou un chemin matérialisé est sans doute une meilleure option pour des arbres principalement en lecture.)"

    C'est bien ça que je veux faire, je vais creuser un peu dans cette direction.

    Donc si j'ai bien compris, il faudrait décrire chaque OneToMany par un paramètre "batch-size", et ensuite pour charger l'arbre d'objets il suffirait d'appeler size sur les objets "points d'entrée" du graphe.


    Pour contribuer un peu, dans ma solution actuelle "a la mano", j'ai pu grandement améliorer les perfs en utilisant un cache pour les opérations d'introspection, le plus coûteux était l'introspection sur les annotations. L'utilisation de JProbe semble indiquer que la plupart du temps est passé dans l'algorithme de parcours du graphe, et non dans les requêtes vers la base.

    Ce qui m'inquiète un peu c'est que par nature notre temps de chargement semble devoir être exponentiel, puisqu'on charge un arbre. C'est une contrainte "topologique" et je ne pense pas qu'on puisse s'en affranchir, mais peut-être que ce que tu veux dire c'est qu'Hibernate va gérer ça mieux que mon algo manuel.

    Sinon, une autre possibilité à laquelle j'avais pensé, et qui si elle était réalisable permettrait de ne pas écrire l'algo de traversée à la main, serait pour chaque entité Owner d'avoir une entité EagerOwner qui surchargerait les getters en EAGER. Par contre il faudrait que ces EagerOwner ne soient pas mappés sur une table, ils ne seraient qu'un expédient pour forcer le chargement. Que penses-tu de cette solution?

  8. #8
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    Citation Envoyé par CharlSka Voir le message
    Bon j'avoue pas avoir tout compris à ton problème, mais est-ce que tu ne pourrais pas te débrouiller avec la méthode Hibernate.initialize(o) (qui force le chargement d'un proxy) ?
    Le problème c'est qu'on veut faire ce que tu dis mais récursivement.

    Exemple: supposons qu'on ait des classes Maitre, Chien, Jouet, MorceauDeJouet.
    On veut charger les Maitre de façon à ce que, sans faire de requête supplémentaire, le client puisse faire maitre.getChiens()[i].getJouet()[i].getMorceauDeJouet()

    Dans notre cas le graphe d'objets est bien plus compliqué que ça, donc on ne peut pas se permettre d'écritre des Hibernate.initialize pour chaque relation explicitement.

  9. #9
    Membre confirmé
    Avatar de grishka
    Inscrit en
    janvier 2003
    Messages
    285
    Détails du profil
    Informations forums :
    Inscription : janvier 2003
    Messages : 285
    Points : 465
    Points
    465
    Par défaut
    Donc si j'ai bien compris, il faudrait décrire chaque OneToMany par un paramètre "batch-size", et ensuite pour charger l'arbre d'objets il suffirait d'appeler size sur les objets "points d'entrée" du graphe.
    oui, comme c'est de l'initialisation tardive, si tu souhaites retourner au code appelant un graphe utilisable en dehors d'une session (histoire d'éviter les lazy initialization error), tu es obliger de parcourir ton graphe d'objet.
    Ton graphe sera donc parcouru 2 fois (1 pour l'init et 1 pour la lecture).
    Dans mon cas, l'application ne nécessitant pas de découpage "pur" entre les couches, on pouvait se permettre de gérer la session hibernate dans la couche présentation et donc accéder au graphe d'objet à la demande...

    Maintenant pour éviter le parcours du graphe :
    - Pas possible en HQL il me semble ! tu ne peux précharger les associations que par jointure, et on retombe sur les produits cartésiens.
    - Par contre tu peux surement via la Criteria API et setFetchMode. (Tu peux spécifier quelles entités tu souhaite précharger en même temps que l'entité racine)
    "Les gens normaux croient que si ca marche, c'est qu'il n'y a rien à reparer. Les ingénieurs croient que si ca marche, c'est que ca ne fait pas encore assez de choses."
    --Scott Adams

  10. #10
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    En tout cas je vois que je ne suis pas le seul à me poser la question:
    http://www.developpez.net/forums/sho...d.php?t=412241

    Citation Envoyé par Grégory Picavet Voir le message
    Dans mon cas, l'application ne nécessitant pas de découpage "pur" entre les couches, on pouvait se permettre de gérer la session hibernate dans la couche présentation et donc accéder au graphe d'objet à la demande...
    Même si je préfèrerais ne pas en arriver là, au moins c'est une solution "marteau-pilon" que je pourrais utiliser si je n'ai plus le choix.

    Citation Envoyé par Grégory Picavet Voir le message
    Maintenant pour éviter le parcours du graphe :
    - Pas possible en HQL il me semble ! tu ne peux précharger les associations que par jointure, et on retombe sur les produits cartésiens.
    Oui, c'est bien ce qu'il me semble avoir compris.

    Citation Envoyé par Grégory Picavet Voir le message
    - Par contre tu peux surement via la Criteria API et setFetchMode. (Tu peux spécifier quelles entités tu souhaite précharger en même temps que l'entité racine)
    Merci, je vais regarder ces pistes. Rien que le nom "setFetchMode" est une douce musique à mes oreilles, je vois ça et je confirme ou infirme que ça permet le chargement récursif.

  11. #11
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    J'ai regardé la doc des Criteria et effectivement la methode createCriteria(String associationPath, String alias, int joinType) semble permettre de spécifier un chemin de parcours du graphe d'objets.

    -Problème technique: comment je fais pour récupérer un objet Session, nécessaire pour utiliser les Criteria?
    Tous les exemples que je trouve sur le net sont dans un contexte JavaSE avec un fichier hibernate.cfg.xml; mais moi je suis en EJB3 et j'ecris mes finders dans un Stateless, dans lequel on injecte le PersistenceContext.
    Jusqu'à maintenant mes finders sont donc écris en utilisant l'EntityManager relatif à ce contexte, je n'ai pas de Session. Peut-on injecter un SessionFactory?

    -Problème d'API: createCriteria(associationPath) semble permettre de spécifier UN chemin de traversée. Ce n'est pas encore tout à fait ce que je veux, car on a besoin de traverser tous les chemins (et en plus on préferais se dispenser de les écrire explicitement).

  12. #12
    Candidat au Club
    Inscrit en
    septembre 2007
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : septembre 2007
    Messages : 9
    Points : 2
    Points
    2
    Par défaut
    Finalement j'en suis resté à ma solution initiale de traversée du graphe à la main. Avec JProbe j'ai pu identifier les points chauds, c'est bien plus rapide maintenant.
    Pour la volumétrie qu'on a pour l'instant, on fera mieux si besoin est.

    Par contre je n'ai rien pu optimiser côté Hibernate, que ce soit avec le cache ou avec le BatchSize, ni en mettant des index sur les FK. Mes tentatives de ce côté ont toutes abouti soit à rien du tout soit à un temps de réponse pire..

    Le gros du temps est désormais passé dans le fetch des collections OneToMany, que je force en appelant size().

Discussions similaires

  1. [RIA Services] Graphes d'objets et includes
    Par anthyme dans le forum Silverlight
    Réponses: 0
    Dernier message: 14/06/2011, 00h35
  2. charger puis exposer des objets avec un ORM
    Par maa dans le forum Général Dotnet
    Réponses: 10
    Dernier message: 11/04/2011, 12h58
  3. Graphe sémantique : objets sémantiques (RFC3)
    Par onjanirina dans le forum Conception (Générale)
    Réponses: 5
    Dernier message: 19/03/2011, 13h17
  4. Graphe d'objet et désallocations
    Par screetch dans le forum C++
    Réponses: 8
    Dernier message: 14/01/2011, 14h35
  5. [Architecture / Services] Graphe d'objets à sauvegarder
    Par mauvais_karma dans le forum Plateformes (Java EE, Jakarta EE, Spring) et Serveurs
    Réponses: 5
    Dernier message: 05/03/2006, 17h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo