Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 606
Dónal Avatar asked Jan 31 '14 17:01

Dónal


3 Answers

  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
like image 193
Andrew Avatar answered Nov 09 '22 01:11

Andrew


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.

like image 35
Dónal Avatar answered Nov 09 '22 00:11

Dónal


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
}  
like image 1
Olencha Avatar answered Nov 09 '22 01:11

Olencha