2010-01-14 3 views
5

Je rencontre des problèmes pour représenter une hiérarchie d'objets dans Hibernate. J'ai cherché autour, et n'ai pas réussi à trouver des exemples faisant ceci ou semblable - vous avez mes excuses si ceci est une question commune.Représentation efficace des hiérarchies dans Hibernate

J'ai deux types que je voudrais persister en utilisant Hibernate: Groupes et Articles.
* Les groupes sont identifiés uniquement par une combinaison de leur nom et de leur parent.
* Les groupes sont organisés en un certain nombre d'arbres, de sorte que chaque groupe a zéro ou un groupe parent.
* Chaque élément peut appartenir à zéro ou plusieurs groupes.

Idéalement, je voudrais une relation bidirectionnelle me permettant d'obtenir:
* tous les groupes un élément est membre de
* tous les éléments qui sont membres d'un groupe particulier ou de ses descendants.
J'ai aussi besoin de pouvoir traverser l'arborescence du groupe depuis le haut pour l'afficher sur l'interface utilisateur.

La structure de l'objet de base ressemblerait idéalement comme ceci:

Au départ, je venais d'un simple bidirectionnel plusieurs à plusieurs entre les objets et les groupes, de sorte que la récupération tous les éléments d'une hiérarchie du groupe requis récursion l'arbre, et la récupération des groupes pour un élément a été un getter simple, à savoir:

class Group { 
    ... 
    private Set<Item> items; 
    private Set<Group> children; 
    ... 
    /** @return all items in this group and its descendants */ 
    Set<Item> getAllItems() { 
     Set<Item> allItems = new HashSet<Item>(); 
     allItems.addAll(this.items); 
     for(Group child : this.getChildren()) { 
      allItems.addAll(child.getAllItems()); 
     } 
     return allItems; 
    } 

    /** @return all direct children of this group */ 
    Set<Group> getChildren() { 
     return this.children; 
    } 
    ... 
} 

class Item { 
    ... 
    private Set<Group> groups; 
    /** @return all groups that this Item is a direct member of */ 
    Set<Group> getGroups() { 
     return this.groups; 
    } 
    ... 
} 

Cependant, cela a donné lieu à des demandes de plusieurs bases de données pour récupérer les éléments d'un groupe avec de nombreux descendants, ou pour récupérer le groupe entier arborescence à afficher dans l'interface utilisateur. Cela semble très inefficace, en particulier avec des arbres plus profonds et plus grands. Existe-t-il une meilleure façon de représenter cette relation dans Hibernate? Est-ce que je fais quelque chose de manifestement faux ou stupide?


Mon seul autre pensée à ce jour était la suivante: Remplacer id, les champs parents et le nom du groupe avec un « chemin » unique chaîne qui spécifie l'ensemble ascendance d'un groupe, par exemple:
/rootGroup
/rootGroup/unenfant
/rootGroup/unenfant/aGrandChild

La table de jointure entre les groupes et les objets contient alors group_path et item_id. Cela résout immédiatement les deux problèmes dont je souffrais auparavant:
1. La hiérarchie de groupe entière peut être extraite de la base de données dans une seule requête et reconstruite en mémoire.
2. Pour récupérer tous les éléments d'un groupe ou ses descendants, nous pouvons choisir parmi group_item où group_path = « N » ou group_path comme « N /% »

Cependant, cela semble vaincre le point d'utiliser Hibernate. Toutes les pensées sont les bienvenues

Répondre

4

Je pense que la vraie question est où voulez-vous que la performance frappe. Si vous déclarez toutes vos relations paresseuses, et tirez ce dont vous avez besoin, alors vous répartissez le coup au fil du temps. Dans de nombreux cas, cela signifie que votre utilisateur (personne ou silicium) perçoit un temps de réaction plus rapide.Même si vous traitez le graphe entier en même temps, vous n'avez pas besoin du graphe entier en mémoire en même temps. D'autre part, en tirant sur l'ensemble du graphique, la performance est mise en avant tout en accélérant la manipulation.

La plus grande différence entre les deux est votre cas d'utilisation spécifique. S'il s'agit d'un serveur Web, en tirant le graphique entier dans la mémoire va tuer le serveur. La plupart des gens ne peuvent pas gérer plus de 7-10 options de toute façon, et l'ensemble du graphique peut facilement submerger l'utilisateur avec des choix rendant l'interface utilisateur difficile à naviguer.

Rappelez-vous aussi ces règles d'optimisation:

  1. Ne pas
  2. Sérieusement, Ne pas. Il est moins cher de lancer du matériel sur un problème de performance, alors il optimise votre code au point qu'il n'est plus maintenable.
  3. Si vous le souhaitez, trouvez le en utilisant un outil de profilage et corrigez-le.
+1

La maxime de Knuth «L'optimisation prématurée est-elle la racine de tout mal? –

+0

Je suppose, comme dans plusieurs cas similaires, que ce n'est pas un problème de grandes structures de données en mémoire mais un nombre croissant d'opérations SQL. Bien que chaque opération soit très rapide, dans les réseaux d'objets complexes, le nombre de requêtes et le temps de latence de la communication feront descendre une application à l'analyse. Dans ce cas, je préfère normalement identifier des parties plus grandes du réseau et utiliser des techniques pour (pré) les récupérer le plus tôt possible (voir la réponse supplémentaire ci-dessous). La mémoire peut être réutilisée facilement. –

+0

@ Matthew Oui, je crois qu'il a dit ça. @Ralf Nous n'avons tout simplement pas l'info. J'essaie juste de couvrir les deux bases. –

3

Dans mes projets passés, j'ai implémenté deux solutions différentes mais comparables à ce problème.

La solution la plus importante (du point de vue de la performance) était basée sur le fait que j'avais beaucoup d'arbres différents de taille moyenne. Au lieu de cartographier toutes les feuilles d'arbre en utilisant des relations bidirectionnelles, chaque feuille a une relation avec la racine des arbres et une seconde avec leur parent direct. En utilisant cette stratégie, il était assez facile d'identifier tous les nœuds racines (FROM arbre t WHERE t.parent IS NULL) et il était vraiment facile de récupérer l'arbre complet basé sur l'élément racine (FROM arbre t WHERE t.root =: root).

La relation "Définir les enfants" a été déclarée attribut @Transient. En utilisant une simple opération utilitaire, il était assez facile de reconstruire la structure arborescente d'origine à tout moment.

L'autre solution traitait d'un petit nombre de très grosses structures arborescentes. Mais heureusement, ces structures avaient une profondeur limitée. Au lieu d'utiliser une seule racine, j'ai cartographié plusieurs niveaux (6 si je me souviens bien) de "parents". De cette façon, il était facile d'extraire et de reconstruire des sous-arborescences complètes à partir de la base de données.

(Ces niveau1 aux relations de Level6 ont été cartographiés comme "paresseux many-to-one" relations et l'application a été fortement dependend sur les caches de 2e niveau.)

+0

Salut Ralf, merci pour la réponse. Cette approche semble vouloir accélérer les choses dans certains cas, mais je ne suis pas sûr que cela s'applique à tous les cas d'utilisation. – Armand

2

Regardez dans des ensembles imbriqués. Voici un bon article:

http://www.developersdex.com/gurus/articles/112.asp

Fondamentalement, vous sacrifiez un peu de performance lorsque la hiérarchie est modifiée afin de simplifier les récupérations profondes, comme ce que vous voulez faire avec des objets en tant que membres de groupes descendants.

+0

très intéressant! Je pense que je devrai le relire quelques fois avant de l'avoir complètement compris. – Armand

+0

Heureux de vous aider. Cela fait quelques années que j'ai travaillé avec eux, mais si vous avez des questions spécifiques. Après avoir relu l'article auquel je me suis connecté, j'ai remarqué qu'il a survolé quelques détails. Voici un article plus explicatif, courtsey de MySQL: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html Ils couvrent des ensembles imbriqués à mi-chemin vers le bas. – Taylor

1

J'ai les mêmes préoccupations concernant le coût élevé en sql qui est causé par l'algorithme récursif lors de l'obtention des enfants d'un nœud. J'ai décidé de créer une nouvelle table dans la base de données qui a plusieurs à une relation avec la table d'origine et insérer une clé étrangère de l'ID de noeud. Et la colonne suivante est l'identifiant de l'enfant. Avec cette structure j'ai persisté la structure d'arbre une fois dans une table de base de données. Donc, il n'est pas nécessaire d'avoir des nœuds enfants récursivement.Je viens d'écrire une requête SQL qui relie deux tables et me donne les enfants d'un nœud particulier.

Questions connexes