Last updated by
4 years ago
Page: Many-to-Many Mapping without Hibernate XML, Version:3
Many-to-Many Mapping without Hibernate XML
One pattern that shows up repeatedly is creating a Many-to-Many relationship using a mapping class. In Rails, the hasMany/through directive provides this functionality. Grails doesn't have an explicit hasMany/through statement like Rails. Rather, Many-to-Many relationships are created implicitly by GORM via your domain classes.A Typical Example: Employees and Teams
This tutorial will show how to create a Many-to-Many relationship between two classes, Employee and Team.A customer wants to you to develop a web page for representing their employees and the teams their employees are on. The customer tells you:- All employees are on one or more teams.
- All teams have one or more employees on them.
- All teams are managed by a single employee.
- An employee can manager multiple teams.
class Employee {
String name
}class Team {
String name
Employee manager
}class Employee {
String name
static hasMany = [managedTeams:Team]
static mappedBy = [managedTeams:"manager"]
}class Team {
String name
Employee manager
static belongsTo = Employee
}% grails shell groovy> new Employee(name:"Alice").addToManagedTeams(name:'A Team').save() groovy> gogroovy> println Employee.findByName("Alice").managedTeams.name groovy> go A Teamgroovy> println Team.findByName("A Team").manager.name groovy> go Alice
The Many-to-Many Map
Now, we need to add the Many-to-Many relationship between Employees and Teams. To do this, we'll use another domain class to map the relationship: Membership.class Membership {
Employee employee
Team team
}class Employee {
String name
static hasMany = [managedTeams:Team, memberships:Membership]
static mappedBy = [managedTeams:"manager"]
}class Team {
String name
Employee manager
static belongsTo = Employee
static hasMany = [memberships:Membership]
}groovy> def alice = Employee.findByName("Alice")
groovy> alice.memberships.each { println it.team.name }
groovy> go
A Teamclass Membership {
Employee employee
Team team static Membership link(employee, team) {
def m = Membership.findByEmployeeAndTeam(employee, team)
if (!m)
{
m = new Membership()
employee?.addToMemberships(m)
team?.addToMemberships(m)
m.save()
}
return m
} static void unlink(employee, team) {
def m = Membership.findByEmployeeAndTeam(employee, team)
if (m)
{
employee?.removeFromMemberships(m)
team?.removeFromMemberships(m)
m.delete()
}
}
}groovy> def bob = Employee.findByName("Bob") groovy> def ateam = Team.findByName("A Team") groovy> Membership.link(bob, ateam) groovy> go===> Membership : 1groovy> bob.memberships groovy> go===> [Membership : 1]groovy> ateam.memberships groovy> go===> [Membership : 1]
Traversing the Membership
It's worth noting that you need to traverse the membership class to find "the other end". For example, you need to go from Employee to Membership to Team. You can do this quickly via collect():groovy> bob.memberships.collect{it.team}
groovy> go===> [Team : 1]class Employee {
String name
static hasMany = [managedTeams:Team, memberships:Membership]
static mappedBy = [managedTeams:"manager"] def teams() {
return memberships.collect{it.team}
}
}class Team {
String name
Employee manager
static belongsTo = Employee
static hasMany = [memberships:Membership] def employees() {
return memberships.collect{it.employee}
}
}groovy> def bob = Employee.findByName("Bob")
groovy> println bob.teams().name
groovy> go
A TeamGORM-y Helpers
Even with the static link/unlink methods on Membership, there is still some "impedence mismatch" with normal GORM-generated methods addTo/removeFrom. So, we can add our own:class Employee {
String name
static hasMany = [managedTeams:Team, memberships:Membership]
static mappedBy = [managedTeams:"manager"] List teams() {
return memberships.collect{it.team}
} List addToTeams(Team team) {
Membership.link(this, team)
return teams()
} List removeFromTeams(Team team) {
Membership.unlink(this, team)
return teams()
}
}class Team {
String name
Employee manager
static belongsTo = Employee
static hasMany = [memberships:Membership] List employees() {
return memberships.collect{it.employee}
} List addToEmployees(Employee employee) {
Membership.link(employee, this)
return employees()
} List removeFromEmployees(Employee employee) {
Membership.unlink(employee, this)
return employees()
}
}groovy> def carl = new Employee(name:"Carl") groovy> def ateam = Team.findByName("A Team") groovy> ateam.addToEmployees(carl) groovy> println "A Team: " + ateam.employees() groovy> println "Carl: " + carl.teams() groovy> goA Team: ["Alice", "Carl"] Carl: ["A Team"]
Lazy Initialization Errors
You may encounter Hibernate Lazy Initialization errors when using this technique, especially if you attempt to access your domain collections from inside GSP templates (via the render template command in GSP pages). There is a work-around.First, use the grails command to install the grails templates into your project:grails install-templates
<!-- Hibernate -->
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.codehaus.groovy.grails.orm.hibernate.support.GrailsOpenSessionInViewFilter
</filter-class>
</filter><filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ~ Hibernate -->