tygerpatch
tygerpatch

Reputation: 55

GORM bidirectional One-to-Many

I'm trying to create a bidirectional one-to-many relationship between Foo and Bar domains in GORM.

Here's what I have so far:

class Bar {
   static belongsTo = [foo: Foo]
}

class Foo {
   Set bars = []
   static hasMany = [bars: Bar]
}

The problem that I'm having is when I go to use the relationship methods, they don't seem to behave the way that I think they should. For example, you would think a statement like "foo.bars.add(bar)" would also set the foo field on the bar argument. But when I call "bar.foo.id" I'm told that the foo field is null. I can fix that problem if I use "bar.foo = foo" instead of "foo.bars.add(bar)". Unfortunately, when I call "foo.bars.size()" it tells me that it is 0.

To get a clearer picture of what I'm talking about, here are my tests:

def testFoo() {
   def foo = new Foo()
   def bar = new Bar()

   foo.bars.add(bar)

   println "foo.bars.size() = ${foo.bars.size()}"
   println "bar.id = ${bar.id}"

   for(def xbar : foo.bars) {
      println "bar.id = ${xbar.id}"
   }

   println "foo.id = ${foo.id}"
   println "bar.foo.id = ${bar?.foo?.id}" // <- is null
}

def testBar() {
   def foo = new Foo()
   def bar = new Bar()

   bar.foo = foo

   println "foo.bars.size() = ${foo.bars.size()}" // <- is 0
   println "bar.id = ${bar.id}"

   for(def xbar : foo.bars) {
      println "bar.id = ${xbar.id}"
   }

   println "foo.id = ${foo.id}"
   println "bar.foo.id = ${bar?.foo?.id}"
}

What am I doing wrong?

Note: I'm running this through an integration test. I've also found that "foo.addToBars(bar)" works the way that I think "foo.bars.add(bar)" and "bar.foo = foo" should work.

Update Here's a quick hack I did that does what I want (using Hibernate and JPA):

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;

import stuff.Foo;

public class MyList<Bar> extends LinkedList {

   private Foo foo;

   public MyList(Foo foo) {
      this.foo = foo;
   }

   @Override
   public boolean add(Object obj) {
      boolean result = super.add(obj);

      try {
         Method barMethod = obj.getClass().getDeclaredMethod("setFoo", Foo.class);
         barMethod.invoke(obj, foo);
      }
      catch(NoSuchMethodException noSuchMethod) {
         noSuchMethod.printStackTrace();
      }
      catch(InvocationTargetException invocationTarget) {
         invocationTarget.printStackTrace();
      }
      catch(IllegalAccessException illegalAccess) {
         illegalAccess.printStackTrace();
      }

      return result;
   }
}

Upvotes: 0

Views: 1717

Answers (1)

Gregg
Gregg

Reputation: 35864

If you read the documentation, you'll learn that, as you found out but are still questioning, the correct way to add items to a collection so they are persisted correctly is to use the addTo* methods.

So in your case, when you said that using addToBars worked, that's the right way to do this. That said, there are some performance hits you take with that. Another way is to do:

bar.foo = foo
bar.save()

The downside is that foo will not contain bar in its existing Set. You'd have to pull it from the database again. It's a bit of a give and take and you just use the best method for your situation.

Upvotes: 2

Related Questions