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