Grails GORM Association Quick Reference

GORM is very functional but not totally intuitive - every time I model relationships I again have to consult the Grails user guide to remember the differences between all the options. So I’ve written a quick-reference. This is a work in progress so there might be errors, sorry!

One to one

"Unidirectional"

class Car {
  Registration registration
}

class Registration {
}

  • Foreign key to Registration in Car table.
  • aCar.registration works. aRegistration.car doesn't.
  • No cascading.

"Bidirectional"

class Car {
  Registration registration
}

class Registration {
  static belongsTo = [car:Car]
}

  • Foreign key to Car in Registration table.
  • Saves and deletes cascade from Car to Registration

"True one-to-one"

class Car {
  static hasOne = [registration:Registration]
}

class Registration {
  Car car
}

  • Foreign key to Car in Registration table
  • Good idea to add a unique constraint on one side, e.g. static constraints = { registration unique:true }

One to many

The main questions to ask yourself are: Do I want cascading deletes? Do I want bi-directional references?

Join Table style

Use this if you want to allow deletes without cascading, and you don't need to reference from the many side to the one side.

class Vehicle {
  static hasMany = [wheels:Wheel]
}

class Wheel {
}

  • Mapped with join table vehicle_wheel.
  • Saves and updates cascaded from Vehicle to Wheel.
  • Deletes of Vehicle not cascaded.
  • A Vehicle has reference to its wheels (aVehicle.wheels works).
  • A Wheel does not have reference to its Vehicle (aWheel.vehicle does not work).

Two-table official style

Use this if you want cascading and you need to reference both ways.

class Vehicle {
  static hasMany = [wheels:Wheel]
}

class Wheel {
  static belongsTo = [vehicle:Vehicle]
}

  • Foreign key to Vehicle in Wheel table. No join table.
  • Forces every Wheel to be associated with a Vehicle, unless you use a nullable:true constraint.
  • A Vehicle holds a reference to its wheels (aVehicle.wheels works).
  • A Wheel holds a reference to its Vehicle (aWheel.vehicle works).
  • Saves, updates and deletes cascaded from Vehicle to Wheel.
  • Saves not cascaded from Wheel to Vehicle.

Two-table undocumented

You see this around the 'net etc, but don't do this. The main difference between this and the official way is that deletes throw a DB error rather than cascading. It works, but as it's not documented I'd recommend to stick to the documented route. See below for the official way to do this.

class Vehicle {
  static hasMany = [wheels:Wheel]
}

class Wheel {
  Vehicle vehicle
}

  • This option is not documented in the reference manual.
  • Foreign key to Vehicle in Wheel table. No join table.
  • Forces every Wheel to be associated with a Vehicle, unless you use a nullable:true constraint.
  • A Vehicle holds a reference to its wheels (aVehicle.wheels works).
  • A Wheel holds a reference to its Vehicle (aWheel.vehicle works).
  • Saves and updates cascaded from Vehicle to Wheel.
  • Deletes not cascaded. Which means you get an exception from your database, because there's no join table so the orphaned Wheel would be referencing a non-existant row.
  • Saves not cascaded from Wheel to Vehicle.

Bi-Directional References Without Cascading Deletes

OK so what should you do if you want to be able to reference both ways but you would rather throw a database error than allow a cascading delete? You use the two-table official style but you change the cascading behaviour to save-update:

 

class Vehicle {
  static hasMany = [wheels:Wheel]


  static mapping = {
    wheels cascade: 'save-update'
  }
}

class Wheel {
  static belongsTo = [vehicle:Vehicle]
}

  • Same properties as two-table official method, but throws a database error when attempting to delete a Vehicle that has Wheels referring to it.

Many to Many

The Only Way

class Address {
  static belongsTo = Recipient
  static hasMany = [recipients:Recipient]
}

class Recipient {
  static hasMany = [addresses:Address]
}

  • Uses join table.
  • An "owning side" must be defined. It's the side that doesn't contain the belongsTo statement. In other words, a belongsTo statement is necessary.
  • Only the owning side can cascade saves (in Hibernate only one side of a many-to-many can take responsibility for managing the relationship).
  • Deletes not cascaded (you would never want that in many-to-many).

Comments

This is a great reference, and I think it might make sense to get it included in the default GORM docs, since, as you know, they are rather cryptic. Anyone who cares about how their classes get mapped to the DB will already be thinking in terms of one-to-one and one-to-many, so it makes sense to lay out the possible configurations the way you have.There are a couple of things I would like to see improved, however:

  1. Corresponding DB table diagrams and/or ERD for each scenario would be fantastic
  2. Showing how to make the one-to-one relationship optional (ie. nullable on one side or the other) would be really useful as well

Other than that, great work, and please consider contributing it to the main Grails documentation.- Rick (cdeszaq)

I realized someone had already written it and here it is!!Thank you!I would only suggest to add information to the "many to many - the only way" about the navigation directions. I think one can navigate both sides, right?

Thanks Marcelo. Yes, I believe that you can navigate both ways. In fact most of the time which side is the owner is irrelevant, as far as I recall the only issue is who can cascade saves. But I'm not working with Grails right now so I may be a little rusty there.

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.