6

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

Dónal
  • 185,044
  • 174
  • 569
  • 824
  • 1
    Re (1) - I just tried this with an empty app with just these four domain classes and I get a one-to-many (the user table has an `organization_id bigint` which is foreign-keyed to the organization table's `id` column). What DB are you using? – Ian Roberts Feb 02 '14 at 21:30
  • @IanRoberts I'm using MySQL – Dónal Feb 03 '14 at 10:53
  • 1
    I suggest to create the database you wish with MySQL. Then, import it to Grails with the [Grails Database Reverse Engineering Plugin](http://www.grails.org/plugin/db-reverse-engineer) – chelder Feb 03 '14 at 14:43
  • @chelder I don't think this will work for me, because there's no way that this plugin can know that the mapping between charity:organisation and company:organisation should be 1:1 rather than 1:N – Dónal Feb 03 '14 at 15:51

3 Answers3

3
  1. I see the same result as IanRoberts for both MySQL and H2. In other words: no join table generated, but the expected organization_id FK in the users table.
  2. With "Table per subclass" mapping (tablePerHierarchy false), you end up with an implied one-to-one relationship in the database. Primary Keys for Charity and Company will have the same value as the PK for the parent Organization. The schema generated by GORM/Hibernate3 doesn't appear to enforce this with referential integrity constraints. It's pure Hibernate magic. A bit more detail here
Andrew
  • 2,239
  • 13
  • 7
  • *The schema generated by GORM/Hibernate3 doesn't appear to enforce this with referential integrity constraints* - this is very surprising, but is consistent with the behaviour I'm seeing – Dónal Feb 04 '14 at 00:48
  • 1
    Yes, this surprised me too. One more option that may or may not suit your needs is the recently-added (and undocumented?) `tablePerConcreteClass` mapping style: http://jira.grails.org/browse/GRAILS-2759 – Andrew Feb 04 '14 at 01:06
  • I've opened a ticket to get this added to the docs: http://jira.grails.org/browse/GRAILS-11069 – Dónal Feb 04 '14 at 01:22
2

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.

Dónal
  • 185,044
  • 174
  • 569
  • 824
1

ORM is not RDBS.

  1. tablePerHierarchy false

so You Have three tables: Organization, Charity, Company. User belongs to only Organization (not Charity or Company). How are you going to get value of specific fields? There is USER. We know ORGANIZATION, but we don't know Charity or Company. I think you underst...

I can suggest you three solutions:

1. tablePerHierarchy true (But you need to have nullable charity\Company -specific fields )

2.

class User {
    static belongsTo = [charity: Charity, company: Company]
}


class Charity {
    String name
    static hasMany = [users: User]
    // charity-specific fields go here
}

class Company {
    String name
    static hasMany = [users: User]
    // company-specific fields go here
}  

3.

class User {
    static belongsTo = [organization: Organization]
}

class Organization {    
    String name
    Charity charity //nullable
    Company company //nullable
    static hasMany = [users: User]
}

class Charity {
    static belongsTo = [organization: Organization]
    // charity-specific fields go here
}

class Company {
    static belongsTo = [organization: Organization]
    // company-specific fields go here
}  
Olencha
  • 418
  • 2
  • 11
  • **1.** I can't use `tablePerHierarchy` because it would require me to introduce invalid `nullable: true` constraints **2.** this duplicates the fields that are command to all types of organisation, which is exactly what I'm trying to avoid **3.** this proposal has the same problems as the one I posed in my question. In other words, removing `abstract` from `Organization` makes no difference to the generated schema – Dónal Feb 03 '14 at 20:11
  • 1. In this case I use: nullable:true AND validator.For example: validator: { val, obj -> obj.isCompany()? val : true } – Olencha Feb 04 '14 at 05:59
  • 1. In this case I use: nullable:true AND validator.For example: validator: { val, obj -> obj.isCompany()? val : true } 2. Yes. It is not DRY, but it is not BIG problem. 3.removing abstract AND removing 'extends Organization' – Olencha Feb 04 '14 at 06:05
  • 1. I guess that would work, but it's not very pretty 3. sorry, I didn't notice that you'd replaced inheritance inheritance with association. I think `hasOne` might be a better mapping than `belongsTo` – Dónal Feb 04 '14 at 11:32