Modeling Relationships with GORM

Exclusive offer: get 50% off this eBook here
Groovy for Domain-Specific Languages

Groovy for Domain-Specific Languages — Save 50%

Extend and enhance your Java applications with Domain Specific Languages in Groovy

£16.99    £8.50
by Fergal Dearle | June 2010 | Java Open Source

The previous article, The Grails Object Relational Mapping (GORM) by Fergal Dearle, author of the book Groovy for Domain-Specific Languages, provided us with an overview of the Grails Object Relational Mapping (GORM).

In this article, we will take a look at how GORM helps in modelling different types of relationships between objects.

(For more resources on Groovy DSL, see here.)

Storing and retrieving simple objects is all very well, but the real power of GORM is that it allows us to model the relationships between objects, as we will now see. The main types of relationships that we want to model are associations, where one object has an associated relationship with another, for example, Customer and Account, composition relationships, where we want to build an object from sub components, and inheritance, where we want to model similar objects by describing their common properties in a base class.

Associations

Every business system involves some sort of association between the main business objects. Relationships between objects can be one-to-one, one-to-many, or many-to-many. Relationships may also imply ownership, where one object only has relevance in relation to another parent object.

If we model our domain directly in the database, we need to build and manage tables, and make associations between the tables by using foreign keys. For complex relationships, including many-to-many relationships, we may need to build special tables whose sole function is to contain the foreign keys needed to track the relationships between objects. Using GORM, we can model all of the various associations that we need to establish between objects directly within the GORM class definitions. GORM takes care of all of the complex mappings to tables and foreign keys through a Hibernate persistence layer.

One-to-one

The simplest association that we need to model in GORM is a one-to-one association. Suppose our customer can have a single address; we would create a new Address domain class using the grails create-domain-class command, as before.

class Address {
String street
String city

static constraints = {
}
}

To create the simplest one-to-one relationship with Customer, we just add an Address field to the Customer class.

class Customer {
String firstName
String lastName
Address address

static constraints = {
}
}

When we rerun the Grails application, GORM will recreate a new address table. It will also recognize the address field of Customer as an association with the Address class, and create a foreign key relationship between the customer and address tables accordingly.

This is a one-directional relationship. We are saying that a Customer "has an" Address but an Address does not necessarily "have a" Customer.

We can model bi-directional associations by simply adding a Customer field to the Address. This will then be reflected in the relational model by GORM adding a customer_id field to the address table.

class Address {
String street
String city
Customer customer

static constraints = {
}
}

mysql> describe address;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | | |
| city | varchar(255) | NO | | | |
| customer_id | bigint(20) | YES | MUL | NULL | |
| street | varchar(255) | NO | | | |
+-------------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)


mysql>

These basic one-to-one associations can be inferred by GORM just by interrogating the fields in each domain class via reflection and the Groovy metaclasses. To denote ownership in a relationship, GORM uses an optional static field applied to a domain class, called belongsTo. Suppose we add an Identity class to retain the login identity of a customer in the application. We would then use

class Customer {
String firstName
String lastName
Identity ident
}

class Address {
String street
String city
}

class Identity {
String email
String password

static belongsTo = Customer
}

Classes are first-class citizens in the Groovy language. When we declare static belongsTo = Customer, what we are actually doing is storing a static instance of a java.lang.Class object for the Customer class in the belongsTo field. Grails can interrogate this static field at load time to infer the ownership relation between Identity and Customer.

Here we have three classes: Customer, Address, and Identity. Customer has a one-to-one association with both Address and Identity through the address and ident fields. However, the ident field is "owned" by Customer as indicated in the belongsTo setting. What this means is that saves, updates, and deletes will be cascaded to identity but not to address, as we can see below. The addr object needs to be saved and deleted independently of Customer but id is automatically saved and deleted in sync with Customer.

def addr = new Address(street:"1 Rock Road", city:"Bedrock")
def id = new Identity(email:"email", password:"password")
def fred = new Customer(firstName:"Fred",
lastName:"Flintstone",
address:addr,ident:id)

addr.save(flush:true)

assert Customer.list().size == 0
assert Address.list().size == 1
assert Identity.list().size == 0

fred.save(flush:true)

assert Customer.list().size == 1
assert Address.list().size == 1
assert Identity.list().size == 1

fred.delete(flush:true)

assert Customer.list().size == 0
assert Address.list().size == 1
assert Identity.list().size == 0

addr.delete(flush:true)

assert Customer.list().size == 0
assert Address.list().size == 0
assert Identity.list().size == 0

Constraints

You will have noticed that every domain class produced by the grails create-domain- class command contains an empty static closure, constraints. We can use this closure to set the constraints on any field in our model. Here we apply constraints to the e-mail and password fields of Identity. We want an e-mail field to be unique, not blank, and not nullable. The password field should be 6 to 200 characters long, not blank, and not nullable.

class Identity {
String email
String password

static constraints = {
email(unique: true, blank: false, nullable: false)
password(blank: false, nullable:false, size:6..200)
}
}

From our knowledge of builders and the markup pattern, we can see that GORM could be using a similar strategy here to apply constraints to the domain class. It looks like a pretended method is provided for each field in the class that accepts a map as an argument. The map entries are interpreted as constraints to apply to the model field.

The Builder pattern turns out to be a good guess as to how GORM is implementing this. GORM actually implements constraints through a builder class called ConstrainedPropertyBuilder. The closure that gets assigned to constraints is in fact some markup style closure code for this builder. Before executing the constraints closure, GORM sets an instance of ConstrainedPropertyBuilder to be the delegate for the closure. We are more accustomed to seeing builder code where the Builder instance is visible.

def builder = new ConstrainedPropertyBuilder()
builder.constraints {
}

Setting the builder as a delegate of any closure allows us to execute the closure as if it was coded in the above style. The constraints closure can be run at any time by Grails, and as it executes the ConstrainedPropertyBuilder, it will build a HashMap of the constraints it encounters for each field.

We can illustrate the same technique by using MarkupBuilder and NodeBuilder. The Markup class in the following code snippet just declares a static closure named markup. Later on we can use this closure with whatever builder we want, by setting the delegate of the markup to the builder that we would like to use.

class Markup {
static markup = {
customers {
customer(id:1001) {
name(firstName:"Fred",
surname:"Flintstone")
address(street:"1 Rock Road",
city:"Bedrock")
}
customer(id:1002) {
name(firstName:"Barney",
surname:"Rubble")
address(street:"2 Rock Road",
city:"Bedrock")
}
}
}
}
Markup.markup.setDelegate(new groovy.xml.
MarkupBuilder())
Markup.markup() // Outputs xml
Markup.markup.setDelegate(new groovy.util.
NodeBuilder())
def nodes = Markup.markup() // builds a node tree

Groovy for Domain-Specific Languages Extend and enhance your Java applications with Domain Specific Languages in Groovy
Published: June 2010
eBook Price: £16.99
Book Price: £27.99
See more
Select your format and quantity:

(For more resources on Groovy DSL, see here.)

One-to-many

A one-to-many relationship applies when an instance of class such as customer is associated with many instances of another class. For example, a customer may have many different invoices in the system, and an invoice might have a sale order object for each line on the invoice.

class Customer {
String firstName
String lastName
static hasMany = [invoices:Invoice]
}

class Invoice {
static hasMany = [orders:SalesOrder]
}

class SalesOrder {
String sku
int amount
Double price
static belongsTo = Invoice
}

To indicate the "has many" associations between Customer/Invoice and Invoice/ SalesOrder, we insert a static hasMany setting. We can also indicate ownership by using the belongsTo setting. In the preceding example, a sales order line has no relevance except on an invoice. We apply a belongsTo setting to bind it to the invoice. The belongsTo setting will cause deletes to cascade when the owning object is deleted. If an invoice is deleted, the delete will cascade to the sales order lines. Invoice does not belong to Customer, so for auditing purposes the invoice object will not be automatically deleted even if the customer is removed.

From the Groovy DSL point of view, hasMany is just a static table containing a map of IDs and Class objects. GORM can analyze this map at load time in order to create the correct table mappings.

GORM will automatically take care of the mapping between the Customer, Invoice, and SalesOrder classes by creating invoice_sales_order and customer_invoice join tables. We can peek at these by using mysql, to confirm that they exist.

mysql> describe customer_invoice;
+---------------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+------------+------+-----+---------+-------+

| customer_invoice_id | bigint(20) | YES | MUL | NULL | |
| invoice_id | bigint(20) | YES | MUL | NULL | |
+---------------------+------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

mysql>

Customer objects are linked to Invoice objects through the customer_invoice join table. Invoices are linked to sales orders via the invoice_sales_order join table, using foreign keys, as we can see from following DDL diagram:

The hasMany setting is defined as a Map containing a key and class for each domain object that is associated. For each hasMany key encountered on a domain class, GORM will inject an addTo(key) method. This translates to an addToOrders method, which is added to Invoice, as the key used was Orders and an addToInvoices method is added to Customer. Invoking these methods will automatically save an order or invoice in the database and also update the join tables with the correct keys.

def fred = new Customer(firstName:"Fred", lastName:"Flintstone")

fred.save()

def invoice = new Invoice()

invoice.addToOrders(new SalesOrder(sku:"productid01",
amount:1, price:1.00))
invoice.addToOrders(new SalesOrder(sku:"productid02",
amount:3, price:1.50))
invoice.addToOrders(new SalesOrder(sku:"productid03",
amount:2, price:5.00))

fred.addToInvoices(invoice)

GORM adds the addTo{association} methods to a domain class by iterating the hasMany map in the class, and updating the MetaClass with a new method for each association that it encounters. Below we can emulate how GORM does just this. In this example, we iterate over the hasMany map of the Customer class. We add a closure method that returns a string corresponding to each map entry that we encounter. The closure names that we add are dynamically generated and include the key values from the hasMany map.

class Customer {
static hasMany = [invoices:Invoice,
orders:SalesOrder]
}
class Invoice {
}
class SalesOrder {
}
Customer.hasMany.each {
def nameSuffix = it.key.substring(0,1).toUpperCase()
def relation = "${nameSuffix}${it.key.substring(1)}"
Customer.metaClass."addTo${relation}" {
"Add to ${relation}"
}
}
def customer = new Customer()
assert customer.addToInvoices() == "Add to Invoices"
assert customer.addToOrders() == "Add to Orders"

When GORM establishes a one-to-many association with a Class, it also adds a field to the class with the same name as the key used in the hasMany map. So we can access the list of orders on an invoice as follows:

invoice.orders.each {println sku + " " + amount + " " + price

Many-to-many

M any-to-many associations are rarer than any of the other associations, and can be tricky to model. In GORM, all we need to do is to make the association bi-directional and give ownership to one side by applying a belongsTo setting. GORM will take care of the rest.

A tunes database needs to model the fact that artistes perform on many songs but also that artistes collaborate on songs, so songs can have many artistes. Modeling this in GORM is the essence of simplicity.

class Artist {
String name
static hasMany = [songs:Song]
}

class Song {
String title
static belongsTo = Artist
static hasMany = [artists: Artist]
}

When maintaining a database, it does not matter whether we add songs to artistes or artistes to songs—GORM will maintain both sides of the relationship.

def song1 = new Song(title:"Rich Woman")
def song2 = new Song(title:"Killing the Blues")

def artist1 = new Artist(name:"Jimmy Page")
def artist2 = new Artist(name:"Alison Krauss")

song1.addToArtists(artist1)
song1.addToArtists(artist2)
artist1.addToSongs(song2)
artist2.addToSongs(song2)

artist1.save()
artist2.save()
println artist1.name + " performs "
artist1.songs.each { println " " + it.title }
println artist2.name + " performs"
artist2.songs.each { println " " + it.title }

println song1.title + " performed by"
song1.artists.each { println " " + it.name }
println song2.title + " performed by"
song2.artists.each { println " " + it.name }

We add both artistes to song1 and then add song2 to both artistes. We only need to save the artiste objects that are the owners of the relationships, and all associations are preserved. The example prints out the following:

Jimmy Page performs
Killing the Blues
Rich Woman
Alison Krauss performs
Killing the Blues
Rich Woman
Rich Woman performed by
Jimmy Page
Alison Krauss
Killing the Blues performed by
Jimmy Page
Alison Krauss

Composition

Composition is used when instead of having separate tables for each business object, we would like to embed the fields of a child object in the table of its parent. In the previous one-to-one examples, it may be useful for us to have Address and Identity classes to pass around in our Groovy code, but we may prefer the data for these to be rolled into one database table. To do this, all we need to do is to add an embedded setting to the Customer domain class, and GORM takes care of the rest.

class Customer {
String firstName
String lastName
Address billing
Address shipping
Identity ident
static embedded = ['billing','shipping','ident']
}

class Address {
String street
String city
}

class Identity {
String email
String password
}

We can see a useful application of embedding in the previous code snippet, where we needed two addresses—one for billing and one for shipping. This does not warrant a full one-to-many association between Customer and Address because we only ever have the two addresses. The fields from Address and Identity get mapped into columns in the customer table, as we can see here.

Inheritance

By default, GORM implements inheritance relationships with a table-per-hierarchy. A class column in the table is used as a discriminator column.

class Account {
double balance
}

class CardAccount extends Account {
String cardId
}

class CreditAccount extends Account{
double creditLimit
}

All of the properties from the hierarchy are mapped to columns in an account table. Entries in the table will have the class column set to indicate what class the object in the entry belongs to. You will note from the upcoming mysql table that all properties in the derived classes are set to nullable. This is one downside of using the table-per-hierarchy approach, but it can be overcome by using the mapping setting.

mysql> describe account;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | | |
| balance | double | NO | | | |
| class | varchar(255) | NO | | | |
| credit_limit | double | YES | | NULL | |
| card_id | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
6 rows in set (0.01 sec)

mysql>

Mapping

We can use the mapping setting to apply a table-per-subclass strategy to inheritance mapping. This will overcome the need to allow all properties to be nullable. Mapping also allows us to map GORM domain classes onto a legacy database, as it gives us fine control over both table and column names in the relation model that we map to.

class Account {
double balance

static mapping = {
table "fin_account"
balance column:"acc_bal"
}
}

The mapping closure is implemented in Grails in a fashion very similar to constraints. The mapping closure is executed, having set its delegate to an instance of HibernateMappingBuilder. Within the closure code for mapping, we can use any of the built-in methods to control how mapping should occur, or we can name the column field itself for more fine-grained control over the mapping of a column. To apply a table-persubclass strategy and turn on caching, we can add the following:

static mapping = {
tablePerSubclass = true
cache = true
}

Querying

We 've already seen in the examples some basic querying with list() and get(). The list method can be used with a number of named parameters that gives us finer control over the result set returned.

/ list all customers
Customer.list().each {
println "${it.firstName} ${it.lastName}"
}
// List the first 10 customers
Customer.list(max:10).each {
println "${it.firstName} ${it.lastName}"
}
// List the next 10 customers
Customer.list(max:10, offset:10).each {
println "${it.firstName} ${it.lastName}"
}
// List all customers in descending order sorted by last name
Customer.list(sort:"lastName",order:"desc").each {
println "${it.firstName} ${it.lastName}"

We 've seen the get() method in action, which uses the database ID to return an object. We can also use the getAll() method when we want to return more than one object, as long as we have the IDs that we need.

def customers = Customer.getAll(2, 4, 5, 7)

Dynamic finders

GORM supports a unique and powerful query function through the use of dynamic finders. A dynamic finder is a pretended method applied to a domain class, that can return one or many queried objects. Finders work by allowing us to invent method names, where the syntax of the query is bound into the method name. All finders are prefixed by findBy or findAllBy.

/ / Find the first customer called Fred
def fred = Customer.findByFirstName("Fred")
// Find all Flintstones
def flintstones = Customer.findAllByLastName("Flintstone")
// Find Fred Flintstone
def fred_flintstoner = Customer.findByFirstNameAndLastName("Fred",
"Flintstone")

Finder names can also include comparators, such as Like, LessThan, and IsNotNull.

// Find all customers with names beginning with Flint
def flints = Customer.findAllByLastNameLike("Flint%")

We can include associations in queries. Here we can find all invoices for Fred:

def fred = Customer.findByFirstNameAndLastName("Fred", "Flintstone")
def invoices = Invoice.findAllByCustomer(fred)

Dynamic finders open up a huge range of possible finder methods that can be used with a class. The more fields there are, the more possible finder methods there are to match. Rather than trying to second-guess what all of the possible combinations might be and adding them to the metaclass, GORM takes a slightly different approach.

From Groovy 1.0, we can add a method called methodMissing to the MetaClass object. methodMissing is called as the last chance saloon before Groovy throws a MethodMissingException.

Grails adds its own methodMissing implementation, which catches all of the missing method invocations with the prefixes find and findAll the first time that they occur. At that point, the actual method implementation is registered in the metaclass, so subsequent calls to that finder will not suffer the overhead.

GORM as a DSL

We have only scratched the surface of what can be done with GORM and Grails. For a more comprehensive view of GORM, take the time to look at the Grails reference at http://www.grails.org.

What is most interesting about GORM is how it achieves its goals through a suite of mini DSLs implemented in Groovy. DataSource configuration is achieved through a markup syntax that can be understood without any prior knowledge of Groovy. Domain classes could be designed and written by a database architect who can also be insulated from the intricacies of the Groovy language.

Summary

In this article, we saw how GORM helps in the modelling and querying of relationships.


Further resources on this subject:


Groovy for Domain-Specific Languages Extend and enhance your Java applications with Domain Specific Languages in Groovy
Published: June 2010
eBook Price: £16.99
Book Price: £27.99
See more
Select your format and quantity:

About the Author :


Fergal Dearle

Fergal is a seasoned software development professional with 23 years of experience in software product development across a wide variety of technologies. He is currently principal consultant with his own software development consulting company, Dearle Technologies Ltd., engaged in design, development, and architecture for new software products for client companies. In the past Fergal has worked in lead architect and developer roles for Candle Corporation on the OMEGAMON product which is now part of IBMs Tivoli product suite as development manager for the Unix implementations of Lotus 1-2-3. In the early 1990s Fergal lead the team at Glockenspiel that developed CommonView, the first object-oriented UI framework for Microsoft Windows. The team was awarded one of the first ever Jolt Productivity Awards by Dr Dobbs Journal.

Books From Packt

Hacking Vim 7.2
Hacking Vim 7.2

NHibernate 2 Beginner's Guide
NHibernate 2 Beginner's Guide

Plone 3.3 Site Administration
Plone 3.3 Site Administration

Liferay Portal 6 Enterprise   Intranets
Liferay Portal 6 Enterprise Intranets

Oracle JRockit: The Definitive Guide
Oracle JRockit: The Definitive Guide

NetBeans Platform 6.9 Developer's Guide
NetBeans Platform 6.9 Developer's Guide

JSF 2.0 Cookbook
JSF 2.0 Cookbook

Amazon SimpleDB Developer Guide
Amazon SimpleDB Developer Guide

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software