Search code examples
grailsgrails-orm

inheritance in Grails domain model


My Grails app's domain model has the following requirements:

  • a user belong to zero or one organisations
  • an organisation is either a charity or a company
  • charities and companies have some some common fields and also some (non-nullable) fields that are unique to each organisation type

I put the common organisation fields into an abstract Organisation class which Charity and Company both extend. I can't store this hierarchy in a single table because there are non-nullable fields that are specific to each organisation type. The relevant parts of the domain model are shown below:

class User {
  String name

  static belongsTo = [organization: Organization]

  static constraints = {
    organization nullable: true
  }
}

abstract class Organization {    
    String name

    static hasMany = [users: User]

    static mapping = {
        tablePerHierarchy false
    }
}

class Charity extends Organization {
  // charity-specific fields go here
} 

class Company extends Organization {
  // company-specific fields go here
}

When I look at the MySQL schema generated from this model, the inheritance relationship between organisation-company and organisation-charity seems to have been completely ignored. Although there is an organisation table with a name column, it has no primary-foreign key relationship with either company or charity


Solution

  • Solved!

    Add the class below to src/java (this class cannot be written in Groovy)

    package org.example;
    
    import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
    import org.hibernate.MappingException;
    import org.hibernate.mapping.JoinedSubclass;
    import org.hibernate.mapping.PersistentClass;
    import org.hibernate.mapping.RootClass;
    
    import java.util.Iterator;
    
    public class TablePerSubclassConfiguration extends GrailsAnnotationConfiguration {
    
        private static final long serialVersionUID = 1;
    
        private boolean alreadyProcessed = false;
    
        @Override
        protected void secondPassCompile() throws MappingException {
            super.secondPassCompile();
    
            if (alreadyProcessed) {
                return;
            }
    
            for (PersistentClass persistentClass : classes.values()) {
                if (persistentClass instanceof RootClass) {
                    RootClass rootClass = (RootClass) persistentClass;
    
                    if (rootClass.hasSubclasses()) {
                        Iterator subclasses = rootClass.getSubclassIterator();
    
                        while (subclasses.hasNext()) {
    
                            Object subclass = subclasses.next();
    
                            // This test ensures that foreign keys will only be created for subclasses that are
                            // mapped using "table per subclass"
                            if (subclass instanceof JoinedSubclass) {
                                JoinedSubclass joinedSubclass = (JoinedSubclass) subclass;
                                joinedSubclass.createForeignKey();
                            }
                        }
                    }
                }
            }
    
            alreadyProcessed = true;
        }
    }
    

    Then in DataSource.groovy set this as the configuration class

    dataSource {
        configClass = 'org.example.TablePerSubclassConfiguration'
        pooled = true
        driverClassName = "org.h2.Driver"
        username = "sa"
        password = ""
        dbCreate = "update"
        url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
    }
    

    I've submitted a pull request to Grails that fixes this. The fix was be included in Grails 2.3.9.