GORM阻止为域创建外键约束

时间:2011-01-22 14:01:31

标签: grails gorm foreign-key-relationship

我正在使用Grails开发基于Web的应用程序。我遇到过一种情况,我想尝试抑制GORM在表中的字段上创建外键约束。

我有一个域类,它是类层次结构的一部分。域类本质上充当到目标域的链接。目标域可以是不同类型的,并且该链接域的每个子类旨在为每种特定类型的链接项提供链接。这些链接项具有一定的共同行为,即实现相同的接口,但不同于它们存储在不同表中的点。

在此链接域表中,有一列表示要链接到的项目的ID。所有链接的项具有相同的基于整数的id。问题是GORM尝试为同一个表列创建多个外键约束,每个链接域子类一个表示不同类型的链接项。我知道我可以为每次id的单独列提供其他id列为null但这看起来有点混乱。如果有办法告诉GORM我不希望它在该列上创建外键约束(因为不同的外键使用相同的列)可以解决问题。

我知道问题出现在引用完整性以及链接键值是否可以放在外表中不存在的列中,但应用程序应该防止这种情况发生。

如果失败了,那么我将不得不手动加载链接项目,而不是依靠GORM自动完成。

3 个答案:

答案 0 :(得分:3)

在相对较短的谷歌搜索后,我找到了Burt Beckwith的博客文章:http://burtbeckwith.com/blog/?p=465,它解释了GORM定制的基础知识。使用以下配置类,我设法阻止创建我不想创建的密钥。在Burt的示例中,需要RootClass,但这不符合我的需要,因此省略了检查。

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;
    }
}

通过将配置类添加到datasource.groovy:

,将配置类引入Grails
dataSource {
    ...
    ...
    configClass = 'com.myapp.DomainConfiguration
}

答案 1 :(得分:0)

你看过文档的这一部分了吗

http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20(GORM).html#5.5.2自定义ORM映射

您可以使用自定义映射DSL覆盖grails的默认持久性语义。

答案 2 :(得分:0)

对于那些使用Grails 3和gorm-hibernate5来解决这个问题的人,我找到了一个基于Graeme对grails-data-mapping #880的评论的解决方案。

我实现了一个自定义SchemaManagementTool并将其添加到应用程序配置中:

hibernate.schema_management_tool = CustomSchemaManagementTool

Hibernate SchemaManagementTool最终将原始SQL命令委托给GenerationTarget(通常为GenerationTargetToDatabase),因此我们的目标是提供我们自己的GenerationTarget。

如果我们可以覆盖HibernateSchemaManagementTool.buildGenerationTargets,这将是最简单的,但遗憾的是,这不会暴露。相反,我们需要开发自己的SchemaCreator和SchemaDropper,并在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
        )
    }
}

对于SchemaCreator和SchemaDropper实现,我们可以分别覆盖doCreation和doDrop。这些基本上是从Hibernate实现复制的,但是使用CustomGenerationTarget而不是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)
    }
}

在这种情况下,我使用相同的CustomGenerationTarget进行创建和删除,但您可以轻松地将其拆分为不同的类。现在我们终于通过扩展GenerationTargetToDatabase并覆盖accept方法来获得回报。通过仅在SQL语句上调用 super.accept 来保留,可以过滤掉不需要的DDL语句。

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
    }
}

这不是最优雅的解决方案,但您可以完成工作。

我还尝试提供自己的SchemaFilterProvider(以及自定义SchemaFilter)。遗憾的是,这只允许过滤表/命名空间 - 而不是外键。