HHH-18286 Add Setting to fail Bootstrapping on Metadata Access Failure

A failure to access the JDBC connection metadata while bootstrapping
Hibernate can lead to ClassCastExceptions during runtime when the
Oracle JDBC driver is used and a BLOB is created by the
NonContextualLobCreator. To avoid this issue, a new JDBC setting is
introduced, which lets the bootstrapping fail when the connection
metadata cannot be obtained.
This commit is contained in:
Daniel Meier 2024-09-15 15:22:13 +02:00
parent d0ddbb5d47
commit bcdc9fc1c7
No known key found for this signature in database
GPG Key ID: B7D668B654858E62
3 changed files with 59 additions and 8 deletions

View File

@ -490,6 +490,21 @@ public interface JdbcSettings extends C3p0Settings, ProxoolSettings, AgroalSetti
*/
String ALLOW_METADATA_ON_BOOT = "hibernate.boot.allow_jdbc_metadata_access";
/**
* Whether failures to access the JDBC {@linkplain java.sql.DatabaseMetaData metadata} should be ignored and the
* bootstrapping process should continue. Hibernate then uses a default JDBC
* {@linkplain org.hibernate.engine.jdbc.env.spi.JdbcEnvironment environment}.
* <p/>
* Failures to access the metadata can result in unexpected runtime errors when accessing the database, since the
* default JDBC environment might not correctly represent the capabilities of the underlying database.
* <p/>
* This setting only takes effect when the {@link #ALLOW_METADATA_ON_BOOT} setting is activated.
*
* @settingDefault {@code true}
* @see #ALLOW_METADATA_ON_BOOT
* @since 6.6
*/
String IGNORE_METADATA_ACCESS_FAILURE_ON_BOOT = "hibernate.boot.ignore_jdbc_metadata_access_failure";
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Deprecated Hibernate settings

View File

@ -13,13 +13,13 @@ import java.util.Collections;
import java.util.Map;
import java.util.StringTokenizer;
import org.hibernate.HibernateException;
import org.hibernate.boot.registry.StandardServiceInitiator;
import org.hibernate.cfg.JdbcSettings;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.batch.spi.BatchBuilder;
import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator;
import org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo;
@ -321,7 +321,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
return temporaryJdbcSessionOwner.transactionCoordinator.createIsolationDelegate().delegateWork(
new AbstractReturningWork<>() {
@Override
public JdbcEnvironmentImpl execute(Connection connection) {
public JdbcEnvironmentImpl execute(Connection connection) throws SQLException {
try {
final DatabaseMetaData metadata = connection.getMetaData();
logDatabaseAndDriver( metadata );
@ -357,7 +357,12 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
);
}
catch (SQLException e) {
log.unableToObtainConnectionMetadata( e );
if ( shouldIgnoreMetadataAccessFailure( configurationValues ) ) {
log.unableToObtainConnectionMetadata( e );
}
else {
throw e;
}
}
// accessing the JDBC metadata failed
@ -387,12 +392,25 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
);
}
catch ( Exception e ) {
log.unableToObtainConnectionToQueryMetadata( e );
if ( shouldIgnoreMetadataAccessFailure( configurationValues ) ) {
log.unableToObtainConnectionToQueryMetadata( e );
}
else {
throw new HibernateException( "Unable to access JDBC metadata", e );
}
}
// accessing the JDBC metadata failed
return getJdbcEnvironmentWithDefaults( configurationValues, registry, dialectFactory );
}
private static boolean shouldIgnoreMetadataAccessFailure(Map<String, Object> configurationValues) {
return getBoolean(
JdbcSettings.IGNORE_METADATA_ACCESS_FAILURE_ON_BOOT,
configurationValues,
true
);
}
private static void logDatabaseAndDriver(DatabaseMetaData dbmd) throws SQLException {
if ( log.isDebugEnabled() ) {
log.debugf(

View File

@ -6,10 +6,6 @@
*/
package org.hibernate.orm.test.boot.database.metadata;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION;
import static org.junit.jupiter.api.Assertions.fail;
import java.lang.reflect.Field;
import java.util.stream.Stream;
@ -50,6 +46,11 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.jboss.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Steve Ebersole
*/
@ -203,6 +204,23 @@ public class MetadataAccessTests {
}
}
@Test
@Jira("https://hibernate.atlassian.net/browse/HHH-18286")
void testDontIgnoreMetadataAccessFailureWhenConnectionCantBeObtained() {
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
registryBuilder.clearSettings();
registryBuilder.applySetting( JdbcSettings.IGNORE_METADATA_ACCESS_FAILURE_ON_BOOT, false );
try (StandardServiceRegistry registry = registryBuilder.build()) {
assertThatExceptionOfType( ServiceException.class )
.isThrownBy( () -> registry.getService( JdbcEnvironment.class ) )
.havingCause()
.isInstanceOf( HibernateException.class )
.withMessage( "Unable to access JDBC metadata" );
}
}
// Ugly hack because neither MINIMUM_VERSION nor getMinimumSupportedVersion()
// can be accessed from this test.
private static DatabaseVersion getVersionConstant(Class<? extends Dialect> dialectClass, String versionConstantName) {