GORM предотвращает создание ограничения внешнего ключа для домена - PullRequest
2 голосов
/ 22 января 2011

Я занимаюсь разработкой веб-приложения в Grails. Я столкнулся с ситуацией, в которой я хотел бы попытаться и запретить GORM создавать ограничение внешнего ключа для поля в таблице.

У меня есть класс домена, который является частью иерархии классов. Класс домена по существу действует как ссылка на целевой домен. Целевой домен может быть разных типов, и каждый из подклассов этого домена ссылки предназначен для предоставления ссылки для каждого конкретного типа связанного элемента. Эти связанные элементы имеют определенное общее поведение, то есть реализуют один и тот же интерфейс, но в остальном отличаются тем, что хранятся в разных таблицах.

В этой таблице доменов ссылок есть один столбец, который представляет идентификатор элемента, с которым связан объект. Все связанные элементы имеют одинаковый целочисленный идентификатор. Проблема заключается в том, что GORM пытается создать несколько ограничений внешнего ключа для одного и того же столбца таблицы, по одному для каждого из подклассов домена ссылок, представляющего связанный тип связанного элемента. Я знаю, что мог бы иметь отдельные столбцы для идентификатора каждого времени, когда другие столбцы идентификатора были бы нулевыми, но это кажется немного грязным. Если бы был способ просто сказать GORM, я бы не хотел создавать ограничение внешнего ключа для этого столбца (потому что разные внешние ключи используют один и тот же столбец), что решило бы проблему.

Я знаю, что возникает вопрос о ссылочной целостности и о том, можно ли поместить значение ключа ссылки в столбец, который не существует во внешней таблице, но приложение должно предотвратить возникновение этой ситуации.

если это не удастся, мне придется вручную загружать связанный элемент, а не полагаться на GORM, чтобы сделать это автоматически.

Ответы [ 3 ]

3 голосов
/ 27 апреля 2012

После относительно короткого поиска в Google я нашел запись в блоге Берта Беквита: http://burtbeckwith.com/blog/?p=465, которая объясняет основы настройки GORM. С помощью следующего класса конфигурации мне удалось предотвратить создание ключа, который я не хотел создавать. В примере Берта требуется 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;
    }
}

Класс конфигурации вводится в Grails путем помещения его в datasource.groovy:

dataSource {
    ...
    ...
    configClass = 'com.myapp.DomainConfiguration
}
0 голосов
/ 21 апреля 2017

Для тех, кто борется с этой проблемой, используя Grails 3 с gorm-hibernate5, я нашел решение, основанное на комментарии Грэма о 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.Вызвав super.accept для сохраняемых операторов SQL, вы можете отфильтровать нежелательные операторы 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 ).К сожалению, это позволяет только фильтровать таблицы / пространства имен, но не внешние ключи.

0 голосов
/ 22 января 2011

смотрели ли вы этот раздел документации

http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20(GORM).html#5.5.2 Пользовательское сопоставление ORM

вы можете переопределить семантику персистентности Grails по умолчанию, используя DSL пользовательского отображения.

...