Search code examples
grailsgrails-ormforeign-key-relationship

GORM prevent creation of a foreign key constraint for a Domain


I am developing a web based application in Grails. I have come across a situation where I would like to try and suppress GORM from creating a foreign key constraint on a field in a table.

I have a domain class which is part of a class hierarchy. The domain class essentially acts as a link to a target domain. The target domain can be of different types and each of the subclasses of this link domain is designed to provide the link for each specific type of linked item. These linked items have certain behaviour in common i.e. implement the same interface but otherwise are different to the point that they are stored in different tables.

Within this link domain table there is one column which represents the id of the item being linked to. All the linked items have the same integer based id. The problem is that GORM tries to create multiple foreign key constraints of this same table column, one for each of the link domain subclasses which represents a different type of linked item. I know I could have separate columns for the id of each time where the other id columns would be null but this seems kind of messy. If there were a way to just tell GORM I don't want it to create a foreign key constraint on that column (because different foreign keys use the same column) that would solve the problem.

I know that the question comes up of referential integrity and whether a link key value could be put in the column which does not exist in the foreign table but the application should prevent this situation from occurring.

failing this then I would have to deal with loading the linked item manually and not rely on GORM to do it automatically.


Solution

  • After a relatively short googling I found Burt Beckwith's blog entry: http://burtbeckwith.com/blog/?p=465 that explains the basics of GORM customization. With the following configuration class I managed to prevent creation of a key I did not want to get created. In Burt's example a RootClass is required, but this did not suit my needs so the checking is omitted.

    package com.myapp;
    
    import com.myapp.objects.SomeClass;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
    import org.hibernate.MappingException;
    import org.hibernate.mapping.ForeignKey;
    import org.hibernate.mapping.PersistentClass;
    import org.hibernate.mapping.RootClass;
    
    import java.util.Collection;
    import java.util.Iterator;
    
    public class DomainConfiguration extends GrailsAnnotationConfiguration {
        private static final long serialVersionUID = 1;
    
        private boolean _alreadyProcessed;
    
        @SuppressWarnings({"unchecked", "rawtypes"})
        @Override
        protected void secondPassCompile() throws MappingException {
            super.secondPassCompile();
    
            if(_alreadyProcessed) {
                return;
            }
    
            Log log = LogFactory.getLog(DomainConfiguration.class.getName());
    
            for(PersistentClass pc : (Collection<PersistentClass>) classes.values()) {
                boolean preventFkCreation = false;
                String fkReferencedEntityNameToPrevent = null;
    
                if("com.myapp.objects.SomeClassWithUnwantedFkThatHasSomeClassAsAMember".equals(pc.getClassName())) {
                    preventFkCreation = true;
                    fkReferencedEntityNameToPrevent = SomeClass.class.getName();
                }
    
                if(preventFkCreation) {
                    for(Iterator iter = pc.getTable().getForeignKeyIterator(); iter.hasNext(); ) {
                        ForeignKey fk = (ForeignKey) iter.next();
    
                        if(fk.getReferencedEntityName().equals(fkReferencedEntityNameToPrevent)) {
                            iter.remove();
                            log.info("Prevented creation of foreign key referencing " + fkReferencedEntityNameToPrevent + " in " + pc.getClassName() + ".");
                        }
                    }
                }
            }
    
            _alreadyProcessed = true;
        }
    }
    

    The configuration class is introduced to Grails by putting it to datasource.groovy:

    dataSource {
        ...
        ...
        configClass = 'com.myapp.DomainConfiguration
    }