Reputation: 31
When upgrading to Spring Boot 3 and Hibernate 6, it seems like property hibernate.hbm2ddl.schema_filter_provider
doesn't work properly. I have a custom implementation of SchemaFilterProvider that excludes my_table
from validation, and I set the hibernate.hbm2ddl.schema_filter_provider
property to use it. I get this error:
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [my_table]
After debugging it, I found that DefaultSchemaFilter.INSTANCE
is in use, and the above property is not being considered:
Validation is done here:
protected void validateTables(
Metadata metadata,
DatabaseInformation databaseInformation,
ExecutionOptions options,
ContributableMatcher contributableInclusionFilter,
Dialect dialect, Namespace namespace) {
final NameSpaceTablesInformation tables = databaseInformation.getTablesInformation( namespace );
for ( Table table : namespace.getTables() ) {
if ( options.getSchemaFilter().includeTable( table )
&& table.isPhysicalTable()
&& contributableInclusionFilter.matches( table ) ) {
validateTable(
table,
tables.getTableInformation( table ),
metadata,
options,
dialect
);
}
}
}
and options
is built here:
public static ExecutionOptions buildExecutionOptions(
final Map<String,Object> configurationValues,
final ExceptionHandler exceptionHandler) {
return buildExecutionOptions(
configurationValues,
DefaultSchemaFilter.INSTANCE,
exceptionHandler
);
}
Meaning DefaultSchemaFilter.INSTANCE
will always be in use.
There's an open bug to Hibernate with no response: https://hibernate.atlassian.net/browse/HBX-2476
How should I exclude my_table
from validation?
Upvotes: 3
Views: 734
Reputation: 281
It is almost a year now since https://hibernate.atlassian.net/browse/HBX-2476 was opened and it is still not resolved. Going to share a nasty workaround that works at least for me, until the bug is fixed.
Using reflection, set the value of the (static final) DefaultSchemaFilter.INSTANCE
to reference an instance of the custom SchemaFilter
that worked with earlier Hibernate versions. This way when Hibernate is going to use the "hard coded" DefaultSchemaFilter.INSTANCE
as filter, it is actually going to use our custom filter implementation. Note that the custom implementation extends DefaultSchemaFilter
.
For example, assume we have this custom implementation that skips validation of PostgreSQL temp tables:
public class CustomSchemaFilter extends DefaultSchemaFilter {
public static final CustomSchemaFilter INSTANCE = new CustomSchemaFilter();
@Override
public boolean includeTable( Table table ) {
return !"pg_temp".equals(table.getSchema());
}
}
We need to set DefaultSchemaFilter.INSTANCE = CustomSchemaFilter.INSTANCE
.
The exact implementation of the reflection code that achieves this depends on Java version. For Java 17, based on https://stackoverflow.com/a/56043252/9061851, I used this:
try {
Field defaultSchemaFilterInstanceField = DefaultSchemaFilter.class.getDeclaredField("INSTANCE");
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);
modifiers.set(defaultSchemaFilterInstanceField, defaultSchemaFilterInstanceField.getModifiers() & ~Modifier.FINAL);
defaultSchemaFilterInstanceField.setAccessible(true);
defaultSchemaFilterInstanceField.set(null, CustomSchemaFilter.INSTANCE);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
However this requires adding --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
to the JVM run parameters.
This piece of code should be called before Hibernate runs its validations. The simplest option is to call it from main()
before loading Spring Boot.
Reminder: this is just a temporary workaround.
Upvotes: 0