GORM 阻止为域创建外键约束

发布于 2024-10-13 16:08:24 字数 518 浏览 7 评论 0原文

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

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

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

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

§对你不离不弃 2024-10-20 16:08:24

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

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
}
想挽留 2024-10-20 16:08:24

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

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.

吻泪 2024-10-20 16:08:24

您看过文档的这一部分

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文