2010-06-29 5 views
5

En Grails, je peux mettre en œuvre un N: 1 relation comme ceci:Quand faut-il hasMany être utilisé pour N: 1 relations dans les classes de domaine de Grails?

class Parent { hasMany = [children:Child] } 
class Child { belongsTo = [parent:Parent] } 

maintenant (si addTo et removeFrom est toujours correctement utilisé) je peux obtenir les enfants d'un parent par l'intermédiaire parent.children.

Mais je peux aussi le faire sans hasMany:

class Parent { } 
class Child { belongsTo = [parent:Parent] } 

Puis-je utiliser Child.findAllByParent (parent) pour tous les enfants.

Ma question: Y a-t-il des raisons importantes pour lesquelles je devrais utiliser hasMany si peut interroger un des enfants de parents de la seconde manière aussi?

Je suppose qu'il est parfois plus facile (et peut-être plus rapide si recherché avec le parent?) De simplement se référer à parent.children, mais d'autre part cette liste peut devenir assez longue quand il y a plusieurs enfants. Et ce que je n'aime pas, c'est que vous devez toujours faire attention à l'addTo ou removeFrom ou effacer la session après avoir ajouté un nouvel enfant avec un parent pour que grails le fasse automatiquement ...

Is la réponse que vous devez simplement utiliser hasMany s'il y a peu d'enfants et ne l'utilisent pas s'il y a beaucoup (pour des raisons de performance), ou est-il plus derrière elle?

Répondre

8

En utilisant hasMany contre belongsTo est plus liée au comportement en cascade que vous voulez spécifier quand une mise à jour/suppression se produit. Dans votre deuxième exemple, la cascade est définie sur ALL du côté enfants et NONE du côté parent. Si vous supprimez un enfant, rien ne se passera sur le parent. Si vous supprimez le parent, tous les enfants seront automatiquement supprimés.

Dans votre première cascade exemple est réglé sur ALL du côté parent, et SAVE-UPDATE du côté de l'enfant. Alors maintenant, vous pouvez faire quelque chose comme:

parent.addToChildren(child1) 
parent.addToChildren(child2) 
parent.addToChildren(child3) 
parent.save(flush:true) 

Et quand vous enregistrez le parent, tous les enfants seront mis à jour.

Abordant quelque chose que vous n'avez pas demandé, vous pourriez aussi avoir probablement quelque chose comme:

class Parent { hasMany = [children:Child] } 
class Child { Parent parent } 

Si vous définissez la relation de l'enfant à des parents de cette façon, vous aurez besoin de gérer manuellement les objets enfants référence le parent lorsque vous supprimez le parent *. (* Ceci corrige une déclaration précédente qui se sont révélées inexactes)

Ainsi, le hasMany/belongsTo a deux considérations principales:

  1. Quel genre de stratégie en cascade voulez-vous exécuter des mises à jour/supprime
  2. Comment êtes-vous le plus susceptible d'accéder aux données, si vous prévoyez avoir besoin de récupérer un ensemble d'enfants pour un parent, ayant une méthode parent.getChildren() est assez pratique.

MISE À JOUR:

Je veux aussi préciser, Gorm ne sera pas hâte-fetch lorsque vous utilisez hasMany; Par défaut, GORM utilise une stratégie de recherche paresseuse afin qu'il n'obtienne pas les enfants avant d'avoir tenté d'accéder au parent.enfants

Si vous voulez une association à avidement alla chercher par défaut, vous pouvez spécifier le mappage approprié:

class Parent { 
    hasMany = [children:Child] 
    static mapping = { 
    children lazy:false 
    } 
} 

Enfin, vous avez dit que vous ne voulez pas que vous avez à vous soucier de la addTo/removeFrom du côté hasMany. Vous ne devriez pas avoir à faire cela si vous sauvegardez avec flush: true.

def parent = Parent.get(id) 
def child = new Child(name:'child1', parent:parent) 
if(child.save(flush:true)) { 
    // both sides of the relationship should be good now 
} 

EDIT: fixe l'ordre inverse des défauts en cascade enfants/parents et les idées fausses corrigées sur la façon dont Gorm a traité des relations sans belongsTo

+0

Merci, donc tout est dans le comportement en cascade. Diriez-vous que la performance n'est pas un problème lorsque la liste à gérer pour les enfants devient très grande? En ce qui concerne votre mise à jour, malheureusement sauver avec flush: vrai n'est pas suffisant pour addTo/removeFrom automatique. Comme je l'ai appris dans mon article Grails soumis https://cvs.codehaus.org/browse/GRAILS-6356, vous devez effacer la session avec sessionFactory.currentSession.clear() si vous voulez réaliser cela à l'intérieur d'un test ou d'un contrôleur . Dans une production échafaudée, cela fonctionne parce que la session est effacée après l'action de sauvegarde/mise à jour de l'enfant. –

+0

Juste testé et il semble que la suppression en cascade est le contraire de ce que vous dites au début: Dans mon premier exemple (avec hasMany), lors de la suppression d'un parent, tous les enfants sont également supprimés. Dans l'exemple SECOND (sans hasMany), les enfants doivent être supprimés manuellement avant de supprimer un parent. –

+0

En fait, mes tests ont également révélé que dans votre exemple avec hasMany et no belongsTo, la suppression du parent n'entraîne PAS un parent nul pour les enfants mais une exception DataIntegrityViolationException ... Je dois mettre les parents des enfants à zéro avant la suppression . –

0

Grande question, et la réponse actuellement acceptée est bonne. Il existe une autre considération de performance importante, à savoir ce qui se passe lorsque vous ajoutez et enregistrez un nouvel enfant. Dans votre premier exemple, Grails par défaut doit charger la liste complète des enfants de la base de données avant d'insérer le nouveau dans l'ensemble, pour garantir l'unicité. Dans le second cas, ce n'est pas le cas, ce qui conduit à de meilleures performances. Vous pouvez contourner ce comportement dans votre premier exemple en définissant les enfants comme une 'Collection' selon http://grails.org/doc/latest/guide/single.html#sets,ListsAndMaps