GORM prevent creation of a foreign key constraint for a Domain

前端 未结 3 1859
一个人的身影
一个人的身影 2021-01-15 13:25

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 fie

相关标签:
3条回答
  • 2021-01-15 13:33

    For those struggling with this problem using Grails 3 with gorm-hibernate5, I found a solution based on Graeme's comment on grails-data-mapping #880.

    I implemented a custom SchemaManagementTool and added it to the application configuration:

    hibernate.schema_management_tool = CustomSchemaManagementTool
    

    The Hibernate SchemaManagementTool ultimately delegates the raw SQL commands to a GenerationTarget (typically GenerationTargetToDatabase), so our goal is to provide our own GenerationTarget.

    This would be easiest if we could override HibernateSchemaManagementTool.buildGenerationTargets, but this is unfortunately not exposed. Instead, we need to develop our own SchemaCreator and SchemaDropper and return them in the CustomSchemaManagementTool:

    class CustomSchemaManagementTool extends HibernateSchemaManagementTool {
        @Override
        SchemaCreator getSchemaCreator(Map options) {
            return new CustomSchemaCreator(this, getSchemaFilterProvider(options).getCreateFilter())
        }
    
        @Override
        SchemaDropper getSchemaDropper(Map options) {
            return new CustomSchemaDropper(this, getSchemaFilterProvider(options).getDropFilter())
        }
    
        // We unfortunately copy this private method from HibernateSchemaManagementTool
        private SchemaFilterProvider getSchemaFilterProvider(Map options) {
            final Object configuredOption = (options == null) ? null : options.get(AvailableSettings.HBM2DDL_FILTER_PROVIDER)
            return serviceRegistry.getService(StrategySelector.class).resolveDefaultableStrategy(
                SchemaFilterProvider.class,
                configuredOption,
                DefaultSchemaFilterProvider.INSTANCE
            )
        }
    }
    

    For the SchemaCreator and SchemaDropper implementation, we can override doCreation and doDrop respectively. These are essentially copied from the Hibernate implementations, but with a CustomGenerationTarget instead of GenerationTargetToDatabase:

    class CustomSchemaCreator extends SchemaCreatorImpl {
        private final HibernateSchemaManagementTool tool
    
        CustomSchemaCreator(HibernateSchemaManagementTool tool, SchemaFilter schemaFilter) {
            super(tool, schemaFilter)
            this.tool = tool
        }
    
        @Override
        void doCreation(Metadata metadata, ExecutionOptions options, SourceDescriptor sourceDescriptor, TargetDescriptor targetDescriptor) {
            final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() )
            final GenerationTarget[] targets = new GenerationTarget[ targetDescriptor.getTargetTypes().size() ]
            targets[0] = new CustomGenerationTarget(tool.getDdlTransactionIsolator(jdbcContext), true)
            super.doCreation(metadata, jdbcContext.getDialect(), options, sourceDescriptor, targets)
        }
    }
    
    class CustomSchemaDropper extends SchemaDropperImpl {
        private final HibernateSchemaManagementTool tool
    
        CustomSchemaDropper(HibernateSchemaManagementTool tool, SchemaFilter schemaFilter) {
            super(tool, schemaFilter)
            this.tool = tool
        }
    
        @Override
        void doDrop(Metadata metadata, ExecutionOptions options, SourceDescriptor sourceDescriptor, TargetDescriptor targetDescriptor) {
            final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() )
            final GenerationTarget[] targets = new GenerationTarget[ targetDescriptor.getTargetTypes().size() ]
            targets[0] = new CustomGenerationTarget(tool.getDdlTransactionIsolator(jdbcContext), true)
            super.doDrop(metadata, options, jdbcContext.getDialect(), sourceDescriptor, targets)
        }
    }
    

    In this case I use the same CustomGenerationTarget for both create and drop, but you could easily split this up into different classes. Now we finally get the payoff by extending GenerationTargetToDatabase and overriding the accept method. By only calling super.accept on SQL statements to keep, you can filter out undesired DDL statements.

    class CustomGenerationTarget extends GenerationTargetToDatabase {
        CustomGenerationTarget(DdlTransactionIsolator ddlTransactionIsolator, boolean releaseAfterUse) {
            super(ddlTransactionIsolator, releaseAfterUse)
        }
    
        @Override
        void accept(String command) {
            if (shouldAccept(command))
                super.accept(command)
        }
    
        boolean shouldAccept(String command) {
            // Custom filtering logic here, e.g.:
            if (command =~ /references legacy\.xyz/)
                return false
            return true
        }
    }
    

    It's not the most elegant solution, but you can get the job done.

    I also tried providing my own SchemaFilterProvider (and a custom SchemaFilter). This unfortunately only allows filtering of tables/namespaces - not foreign keys.

    0 讨论(0)
  • 2021-01-15 13:44

    have you looked at this section of the documentation

    http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20(GORM).html#5.5.2 Custom ORM Mapping

    you can override grails's default persistence semantics by using the custom mapping DSL.

    0 讨论(0)
  • 2021-01-15 13:47

    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
    }
    
    0 讨论(0)
提交回复
热议问题