【问题标题】:GORM prevent creation of a foreign key constraint for a DomainGORM 阻止为域创建外键约束
【发布时间】:2011-01-22 14:01:30
【问题描述】:

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

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

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

我知道问题来自于参照完整性以及是否可以将链接键值放入外部表中不存在的列中,但应用程序应防止这种情况发生。

如果不这样做,我将不得不手动加载链接的项目,而不是依赖 GORM 自动完成。

【问题讨论】:

  • 您能发布一些课程和预期的表格吗?您详细描述了您想要的内容,但使用类更好。如果您这样做,我可能会比我的第一个答案提供更多帮助。

标签: grails grails-orm foreign-key-relationship


【解决方案1】:

在相对较短的谷歌搜索之后,我找到了 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
}

【讨论】:

  • 对于带有 Hibernate 5 的 Grails 3,您有类似的解决方案吗?
  • @Viriato 不幸的是没有。
  • 我为 Grails 3 / Hibernate 5 添加了另一个答案。
【解决方案2】:

对于那些使用 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)。不幸的是,这只允许过滤表/命名空间 - 而不是外键。

【讨论】:

  • 我会试一试,但在您的解决方案中,它声明要删除外键?通过查看您提供的 4 个课程,我不清楚。在上面的 grails 2 解决方案中,我也有它,以便它从某个包下的域中的 PersistentClass 对象中删除检查约束、索引唯一键和列。这是为了确保我们映射的供应商表不会被 Grails 以任何方式(即使在开发环境中)修改。
  • 在 CustomGenerationTarget.accept 方法中过滤外键(不传递给 super)。在这种情况下,我正在寻找“references legacy.xyz”SQL 语句(PostgreSQL 外键语句)。
  • 所以我们能够对其进行测试,并且我们能够在“create-drop”模式下运行时过滤掉 fk 生成,但是当我们在 dbCreate =“update”模式下运行时,accept 方法永远不会被击中。有什么想法吗?
  • @Viriato SchemaManagementTool 还有一个 getSchemaMigrator 方法,我在这个例子中没有覆盖它。也许这被称为 dbCreate = "update"?
  • 您是否成功地将其用于您的项目?我在自定义管理工具中添加了一个 getSchemaMigrator,并告诉它使用在 doMigration 覆盖方法中接受目标 [0] = new CustomGenerationTarget 的 CustomSchemaMigrator,但我遇到内部异常有什么想法吗?
【解决方案3】:

你看过这部分的文档吗

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

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

【讨论】:

  • 我已通读该部分并且已经在我的应用程序中广泛使用了自定义映射 DSL。我已经在文档中搜索了一段时间,但没有找到更改外键创建行为的方法。我开始怀疑我想做的事情目前是不可能的。
猜你喜欢
  • 2021-07-11
  • 2021-12-09
  • 2021-07-02
  • 1970-01-01
  • 2019-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多