Tetrinity
Tetrinity

Reputation: 1105

List vs Set when adding a GORM domain class

Say I have the following (heavily simplified) GORM domain classes:

class PhoneCall extends Interaction {
    Survey survey
}

class Survey {
    String campaignCode
    Integer clientId
    Boolean isDynamic
    List interactions

    static constraints = {
        campaignCode unique: true, nullable: false
        clientId nullable: true
        isDynamic nullable: true
    }

    static hasMany = [interactions: Interaction]
}

class Interaction {
    String clazz
    Instant dateCreated

    static constraints = {
    }

    static mapping = {
        tablePerHierarchy false
        autoTimestamp false
    }

    def beforeInsert() {
        dateCreated = Instant.now()
    }
}

I have the following simple code to set up these classes for a test:

def survey = new Survey(campaignCode: "TEST", isDynamic: true).save(failOnError: true, flush: true)
def phoneCall = new PhoneCall(survey: survey, clazz: PhoneCall.name).save(failOnError: true)

This fails with the following stack trace:

org.springframework.dao.DataIntegrityViolationException: could not insert: [uk.co.nttfundraising.onitfhi.domain.PhoneCall]; SQL [insert into phone_call (id) values (?)]; constraint [survey_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not insert: [uk.co.nttfundraising.onitfhi.domain.PhoneCall]
    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:643)
    at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
    at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:412)
    at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:339)
    at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.performSave(SavePersistentMethod.java:56)
    at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.doInvokeInternal(AbstractSavePersistentMethod.java:215)
    at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:63)
    at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.save(HibernateGormInstanceApi.groovy:196)

However, if I remove the line List interactions from Survey (making interactions into a Set), everything works fine. There are also no problems if I use SortedSet interactions, though the generated database schema doesn't seem to have any notion of order so I'm unsure about that solution. Google mostly suggests not saving the Survey (e.g. this blog post) but I've tried this to no avail.

It's only the List that fails, and it causes the insert into PhoneCall to completely ignore my Survey! What's going on?

Upvotes: 0

Views: 585

Answers (1)

Emmanuel Rosa
Emmanuel Rosa

Reputation: 9885

A caveat to using a List is that the item you add to it cannot be save()d prior to adding to the List. But more importantly, the proper way to add items to a collection when using a one-to-many association is to use survey.addToInteractions(), See addTo*(). But first, you need a proper association...

class PhoneCall extends Interaction {
    static belongsTo = [survey: Survey]
}

By replacing the Survey property with belongsTo, you get a proper bi-directional one-to-many association. Then, you can use/test it like this:

def survey = new Survey(campaignCode: "TEST", isDynamic: true)

survey.addToInteractions(new PhoneCall(survey: survey, clazz: PhoneCall.name))
survey.save(failOnError: true, flush: true)

Notice that the PhoneCall is never explicitly saved, and PhoneCall.survey is not explicitly assigned. All of this gets taken care of when survey.save() is called.

Once saved, someSurvey.interactions[index].survey will reference the someSurvey.

Upvotes: 1

Related Questions