One to Many Mapping Unidirectional Default
When mapping two objects with a one to many relationship and making that relationship unidirectional, grails provides a simple default way to map this to the database. Take an example of Company and Employee. A Company can have many Employees. Here is a code snippet of how to map these objects.
class Company {
String name
String industry
static hasMany = [employees: Employee]
}
class Employee {
String name
Long yearsEmployed
static belongsTo = Company
}
Notice a few things about the GORM mappings. Company has many employess, as noted by the hasMany attribute. An Employee is dependent on the life of a Company, as expressed by the belongsTo attribute. In other words, belongsTo enables cascade delete so that when a Company is deleted, the Employees for the company are deleted as well. GORM generates a database ddl where the COMPANY table is associated with a EMPLOYEE table through a third association table (also known as a join table).
Delete Fails Due to Join Table
In GORM, if you have access to a company instance, then the call company.delete() works fine and the whole object graph is deleted.
However, with this domain it seems like you should be able to delete an Employee from a Company by calling delete() on an employee instance object. For example, in UI scaffolding screens, say you access a Company, then click on an Employee, and use the Delete button to zap an employee. A referential integrity SQL exception occurs!
This is because scaffolding code calls something like this on the Employee UI page, and the code fails.
def employee = Employee.get(params.id)
employee.delete()
This failure is due to the join table having a foreign reference to the EMPLOYEE table. You cannot delete a row from a table that is being reference by another table, due to referencial integrity. Thus, we have a mapping problem.
One to Many Mapping with Foreign Key
There are a few ways top get around this issue. First if you are a stickler to keep a unidirectional relationship, you can map the relationship with a foreign key instead of with a join table. Use the hibernate DSL shown below. The EMPLOYEE table can have a foreign key back to the COMPANY table.
class Company {
String name
String industry
static hasMany = [employees: Employee]
static mapping = {
employees column:'COMPANY_EMPLOYEE_ID',joinTable: false
}
}
class Employee {
String name
Long yearsEmployed
static belongsTo = Company
}
Another option is the allow a birectional one to many relationship. This means the employee will have a reference to its parent company. This also creates a foreign key in the EMPLOYEE table which references back to COMPANY.
class Company {
String name
String industry
static hasMany = [employees: Employee]
}
class Employee {
String name
Long yearsEmployed
static belongsTo = [company: Company]
}
Both these mappings allow the following code to work:
def employee = Employee.get(params.id)
employee.delete()
Design Considerations
The reason hibernate creates a join table by default is so that it can enforce the "not-null" constraint of the child object. In other words, it makes the assumption that a Company must have at least one Employee. Cascading is still enabled, but to delete a Employee you have to go through the Company object and remove a particular Employee from the collection.
If you don't agree with this assumption, then you can map the one to many relationship with a foreign key.
Brilliant explanation - well done.
ReplyDeleteThanks. I ran into a few more peculiar situations, I just have to find time to document them
ReplyDeletecan you please answer to this:
ReplyDeletehttp://stackoverflow.com/questions/8670861/grails-multiple-tables-in-single-domain-class/8672990#8672990
Thanks a million! Wasted quite a bit of time trying to solve this issue before finding this.
ReplyDeleteInteresting Article
ReplyDeleteSpring Hibernate Online Training | Hibernate Training in Chennai
Hibernate Online Training | Java Online Training | Java EE Online Training
Sir i have a problem with OneToMany Mapping. I am using Set and jointable in my project.The problem is while update the old records new rows inserted in the jointable
ReplyDeleteTry making it a list. Or try making it in two different translations
ReplyDeleteSorry Sir i didn't understand this my code i have three classes with one to many relationship
ReplyDeleteHotelDetail.groovy
enum StarRating {
THREE_STAR, FOUR_STAR, FIVE_STAR, SEVEN_STAR
}
class HotelDetail implements Serializable, Comparable {
UUID id
Hotel hotel
StarRating starRating
Set roomTypes
Set attachments
boolean checkoutPeriodFixed
LocalTime startTime
LocalTime endTime
Organization organization
LocalDateTime dateCreated
User createdBy
LocalDateTime lastUpdated
User updatedBy
static hasMany = [roomTypes : RoomType,attachments : Attachment]
static constraints = {
attachments nullable : true
lastUpdated nullable : true
updatedBy nullable : true
}
static mapping = {
id generator: 'uuid2', type: 'pg-uuid'
version false
hotel column : 'HOTEL_ID'
createdBy column : 'CREATED_BY'
updatedBy column : 'UPDATED_BY'
roomTypes lazy:true, fetch:'select', joinTable: [name: 'HOTEL_DETAIL_ROOM_TYPE',
key: 'HOTEL_DETAIL_ID',
column: 'ROOM_TYPE_ID']
attachments lazy:true, fetch:'select', joinTable: [name: 'HOTEL_DETAIL_ATTACHMENT',
key: 'HOTEL_DETAIL_ID',
column: 'ATTACHMENT_ID']
}
def beforeInsert = {
dateCreated = new LocalDateTime()
createdBy = AccountUtils.loggedInUser
}
def beforeUpdate = {
lastUpdated = new LocalDateTime()
updatedBy = AccountUtils.loggedInUser
}
}
RoomType.groovyclass
RoomType implements Serializable, Comparable {
UUID id
String name
String description
int maxOccupancy
int sizeInMeter
int size
String unit
boolean childAllowed
PickList roomCategory
Set attachments
int numRooms
LocalDateTime dateCreated
User createdBy
LocalDateTime lastUpdated
User updatedBy
Organization organization
static hasMany = [ attachments : Attachment]
static constraints = {
unit nullable : true
lastUpdated nullable : true
updatedBy nullable : true
description nullable : true
maxOccupancy validator : {return it !=0}
}
static mapping = {
id generator: 'uuid2', type: 'pg-uuid'
version false
attachments lazy:true, fetch:'select', joinTable: [name: 'ROOM_TYPE_ATTACHMENT',
key: 'ROOM_TYPE_ID',
column: 'ATTACHMENT_ID']
createdBy column : 'CREATED_BY'
updatedBy column : 'UPDATED_BY'
}
def beforeInsert = {
dateCreated = new LocalDateTime()
createdBy = AccountUtils.loggedInUser
}
def beforeUpdate = {
lastUpdated = new LocalDateTime()
updatedBy = AccountUtils.loggedInUser
}
}
Attachment.groovy
class Attachment implements Serializable, Comparable{
UUID id
String fileName
String ext
Organization organization
static constraints = {
}
static mapping = {
id generator: 'uuid2', type: 'pg-uuid'
version false
}
}
Save and Fetch Works Correctly.My problem is while i update any one of the sets, for ex in Set roomTypes .
if i fecth and modify some changes in it.while i update in jointables one new row created whatever i modifyied that id is duplicate in join tables
Thanks for sharing this informative content , Great work
ReplyDeleteLeanpitch provides online training inScrum Master during this lockdown period everyone can use it wisely.
CSM online certification
This is a creative blog thanks for sharing. Yellowstone Coat
ReplyDeletereally helpful
ReplyDeleteJoe Biden Silver Leather Jacket
Bfarf stock Real-Time Overview Of A Stock, Including Recent And Historical Price Charts, News, Events, Analyst Rating Changes And Other Key Stock Information.
ReplyDeleteBfarf stock Overview: Stay ahead of the market with our live and real time stock market overview. Get all of your favorite stocks in one place, and get alerted to live news at the same time!
ReplyDeleteBlogs can support and promote charitable causes, encouraging readers to donate or participate in philanthropic activities. NordVPN Offer Year Blogs can be monetized through affiliate marketing, sponsored content.
ReplyDelete