When should hasMany be used for N:1 relationships in grails domain classes?
Asked Answered
A

2

5

In grails, I can implement an N:1 relationship like this:

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

Now (if addTo and removeFrom is always properly used) I can get a Parent's children via parent.children.

But I can also do it without hasMany:

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

Then I have to use Child.findAllByParent(parent) to get all children.

My question: Are there any important reasons why I should use hasMany if can query a parent's children in the second way as well?

I guess that it's sometimes easier (and perhaps faster if eager-fetched together with the parent?) to just refer to parent.children, but on the other hand this List can become rather long when there are several children. And what I don't like about hasMany either is that you always have to take care about the addTo or removeFrom or to clear the session after adding a new Child with a Parent so that grails does this automatically...

Is the answer that you should simply use hasMany if there are few children and don't use it if there are many (for performance reasons), or is there more behind it?

Allgood answered 29/6, 2010 at 14:59 Comment(0)
E
8

Using hasMany versus belongsTo is more related to the cascading behavior you want to specify when an update/delete occurs. In your second example, the cascading is set to ALL on the children side and NONE on the parent side. If you delete a child, nothing will happen on the parent. If you delete the parent, all the children will automatically be deleted.

In your first example cascading is set to ALL on the parent side, and SAVE-UPDATE on the child side. So now you can do something like:

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

And when you save the parent, all the children will be updated.

Touching on something you didn't ask, you could also presumably have something like:

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

If you define the relationship from Child to Parent in this way, you will need to manually manage the Child objects that reference the parent when you delete the parent*. (*this corrects a previous statement that proved to be inaccurate)

So, the hasMany/belongsTo has two main considerations:

  1. What kind of cascading strategy do you want to execute on updates/deletes
  2. How are you most likely going to access the data, if you expect to need to retrieve a set of children for a parent, having a parent.getChildren() method is pretty convenient.

UPDATE:

I also want to clarify, GORM will not eager-fetch when you use hasMany; by default GORM uses a lazy fetch strategy so it won't get the children until attempt to access parent.children

If you want an association to be eagerly fetched by default, you can specify the appropriate mapping:

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

Finally, you mentioned that you don't like that you have to worry about the addTo/removeFrom on the hasMany side. You shouldn't have to do this if you save with 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: fixed reversed order of child/parent cascade defaults and corrected misconception regarding how gorm handled relationships without belongsTo

Efrainefram answered 29/6, 2010 at 16:40 Comment(5)
Thank you, so it's all about the cascading behaviour. Would you say that performance is not an issue when the list to manage for the children becomes very large? Regarding your update, unfortunately saving with flush:true is not sufficient for automatic addTo / removeFrom. As I learned in my submitted grails issue cvs.codehaus.org/browse/GRAILS-6356, you need to clear the session with sessionFactory.currentSession.clear() if you want to achieve this WITHIN a test or a controller. In a scaffolded production, it works because the session is cleared after the Child's save / update action.Rori
Just tested it and it seems that the cascaded deletion is the opposite of what you state in the beginning: In my FIRST example (with hasMany), when deleting a parent, all children are deleted as well. In the SECOND example (without hasMany), children have to be deleted manually before deleting a parent.Rori
Actually my testing also revealed that in your example with hasMany and no belongsTo, the deletion of the parent does NOT cause the children to have a null parent but throws a DataIntegrityViolationException... I have to set the children's parents to null before the deletion.Rori
Hmm, sorry about that, I guess I was mistaken. From the Grails documentation: "If you do not define belongsTo then no cascades will happen and you will have to manually save each object."Efrainefram
I corrected my answer, I switched the order of the ALL/SAVE-UPDATE by mistake, sorry about that; it should be correct now.Efrainefram
U
0

Great question, and the currently accepted answer is good. There is one other important performance consideration, which is what happens when you add and save a new child. In your first example, Grails by default has to load the entire list of children from the database before inserting the new one into the Set, to guarantee uniqueness. In the second case, it does not, which leads to much better performance. You can get around this behaviour in your first example by defining the children as a 'Collection' as per http://grails.org/doc/latest/guide/single.html#sets,ListsAndMaps

Uxorial answered 6/1, 2012 at 7:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.