Reputation: 475
We are trying to save multiple domain objects that are chained together with foreign key constraints and are having trouble getting GORM to work.
Domain classes:
class Degree {
/* Default (injected) attributes of GORM */
Long id
Long version
/* Automatic timestamping of GORM */
Date dateCreated
Date lastUpdated
String department
String subject
String catalogYear
String level
String type
Long totalCredits
Double cumulativeGPArequired
String capstone
String comment
String updatedBy
static hasMany = [degreeBlocks: DegreeBlock] // tells GORM to associate other domain objects for a 1-n mapping
static mapping = {
sort "id"
version false
table 'degree'
columns{
id column: 'id'
department column: 'department'
subject column: 'subject'
catalogYear column: 'catalog_year'
level column: 'degree_level'
type column: 'type'
totalCredits column: 'total_credits'
cumulativeGPArequired column: 'cume_gpa_req'
capstone column: 'capstone'
comment column: 'degree_comment'
dateCreated column: 'date_created'
lastUpdated column: 'last_updated'
updatedBy column: 'updated_by'
}
id generator:'sequence', params:[sequence:'degree_id_sequence']
}
static constraints = {
department(nullable: true, maxSize: 30)
subject(nullable: true, maxSize: 30 )
catalogYear(nullable: true, maxSize: 9 )
level(nullable: true, maxSize: 15)
type(nullable: true, maxSize: 30)
totalCredits(nullable: true, maxSize: 3)
cumulativeGPArequired(nullable: true, maxSize: 3)
capstone(nullable: true, maxSize: 30)
comment(nullable: true, maxSize: 2000)
lastUpdated(nullable: true)
updatedBy(nullable: true)
}
@Override // Override toString for a nicer / more descriptive UI
public String toString() {
return "${subject}";
}
}
class DegreeBlock {
/* Default (injected) attributes of GORM */
Long id
Long version
/* Automatic timestamping of GORM */
Date dateCreated
Date lastUpdated
String blockType
String comment
static hasMany = [courseBlocks: CourseBlock] // TELLS GORM TO ASSOCIATE OTHER DOMAIN OBJECTS FOR A 1-N MAPPING
static belongsTo = [degree: Degree] // TELLS GORM TO CASCADE COMMANDS: E.G., DELETE THIS OBJECT IF THE "PARENT" IS DELETED.
static mapping = {
sort "id"
version true
table 'degree_block'
columns{
id column: 'id'
level column: 'degree_level'
blockType column: 'block_type'
comment column: 'block_comment'
dateCreated column: 'date_created'
lastUpdated column: 'last_updated'
}
id generator:'sequence', params:[sequence:'degree_block_id_sequence']
}
static constraints = {
blockType(nullable: true, maxSize: 50)
comment(nullable: true, maxSize: 2000)
lastUpdated(nullable: true)
}
/*
* Methods of the Domain Class
*/
@Override // Override toString for a nicer / more descriptive UI
public String toString() {
return "${blockType}";
}
}
class CourseBlock {
/* Default (injected) attributes of GORM */
Long id
Long version
/* Automatic timestamping of GORM */
Date dateCreated
Date lastUpdated
String name
String rule
String credits
String eval
String prereqNotes
String comment
static hasMany = [courses: Course] // tells GORM to associate other domain objects for a 1-n mapping
static belongsTo = [degreeBlock: DegreeBlock] // tells GORM to cascade commands: e.g., delete this object if the "parent" is deleted.
static mapping = {
sort "id"
version false
table 'degree'
columns{
id column: 'id'
name column: 'name'
rule column: 'rule'
credits column: 'credits'
eval column: 'eval'
prereqNotes column: 'prereq_notes'
comment column: 'course_comment'
dateCreated column: 'date_created'
lastUpdated column: 'last_updated'
}
id generator:'sequence', params:[sequence:'course_block_id_sequence']
}
static constraints = {
name(nullable: true, maxSize: 80)
rule(nullable: true, maxSize: 80 )
credits(nullable: true, maxSize: 10 )
eval(nullable: true, maxSize: 4)
prereqNotes(nullable: true, maxSize: 2000)
comment(nullable: true, maxSize: 2000)
lastUpdated(nullable: true)
}
/*
* Methods of the Domain Class
*/
@Override // Override toString for a nicer / more descriptive UI
public String toString() {
return "${name}";
}
}
class Course {
/* Default (injected) attributes of GORM */
Long id
Long version
/* Automatic timestamping of GORM */
Date dateCreated
Date lastUpdated
String subject
String courseNumber
String title
String credits
String term
static belongsTo = [courseBlock:CourseBlock] // tells GORM to cascade commands: e.g., delete this object if the "parent" is deleted.
static mapping = {
sort "id"
version false
table 'degree'
columns{
id column: 'id'
subject column: 'subject_code'
courseNumber column: 'course_number'
title column: 'title'
credits column: 'credits'
term column: 'term'
dateCreated column: 'date_created'
lastUpdated column: 'last_updated'
}
id generator:'sequence', params:[sequence:'course_id_sequence']
}
static constraints = {
subject(nullable: true, maxSize: 5 )
courseNumber(nullable: true, maxSize: 5 )
title(nullable: true, maxSize: 80)
credits(nullable: true, maxSize: 8)
term(nullable: true, maxSize: 10)
lastUpdated(nullable: true)
}
/*
* Methods of the Domain Class
*/
@Override // Override toString for a nicer / more descriptive UI
public String toString() {
return "${subject} ${courseNumber}";
}
}
Service method code:
def saveDegree(params){
def degree = new Degree(params).save()
def degreeBlock = new DegreeBlock(params, degree:degree)
degree.addToDegreeBlocks(degreeBlock)
degreeBlock.save(flush:true)
def courseBlock = new CourseBlock(params, degreeBlock:degreeBlock)
degreeBlock.addToCourseBlocks(courseBlock)
courseBlock.save(flush:true)
def course = new Course(params, courseBlock:courseBlock)
courseBlock.addToCourses(course)
course.save(flush:true)
return degree
}
We tried following along here: How do I save GORM objects with multiple many-to-one relationships?
But it gives us the error:
| Error 2013-09-12 11:03:12,421 [http-bio-8080-exec-10] ERROR util.JDBCExceptionReporter - ORA-02291: integrity constraint (FK1_DEGREE_ID) violated - parent key not found
| Error 2013-09-12 11:03:12,424 [http-bio-8080-exec-10] ERROR events.PatchedDefaultFlushEventListener - Could not synchronize database state with session
Message: Could not execute JDBC batch update
Line | Method
->> 49 | saveDegree in degreebuilder.DegreebuilderService
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 50 | save in degreebuilder.DegreeController
| 195 | doFilter . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 724 | run . . . in java.lang.Thread
Caused by BatchUpdateException: ORA-02291: integrity constraint (FK1_DEGREE_ID) violated - parent key not found
->> 343 | throwBatchUpdateException in oracle.jdbc.driver.DatabaseError
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 10698 | executeBatch in oracle.jdbc.driver.OraclePreparedStatement
| 297 | executeBatch in org.apache.commons.dbcp.DelegatingStatement
| 49 | saveDegree in degreebuilder.DegreebuilderService
| 50 | save . . . in degreebuilder.DegreeController
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 724 | run in java.lang.Thread
Walking through it with the debugger, it seems that the initial degree isn't created until the return statement at the bottom of the method, even though I thought (flush:true)
was supposed to force the persistance?
I'm just trying to do the saves the easiest and most correct way possible, and thought GORM could handle it this way, but maybe not.
Upvotes: 0
Views: 466
Reputation: 50245
Your case is one-to-many instead of many-to-one.
Try this instead. The parent has to be persisted first and eventually the children, you do not need to flush
the children every time it is associated to its parent but finally the root parent can be flushed to witness the cascade behavior.
def saveDegree(params){
def degree = new Degree(params)
def degreeBlock = new DegreeBlock(params)
degree.addToDegreeBlocks(degreeBlock)
def courseBlock = new CourseBlock(params)
degreeBlock.addToCourseBlocks(courseBlock)
def course = new Course(params)
courseBlock.addToCourses(course)
degree.save(flush:true)
return degree
}
Upvotes: 1