diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java
index a4b7062ad590..f7905979fb03 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java
@@ -8,6 +8,7 @@
import org.hibernate.Incubating;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
+import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
import org.hibernate.query.Query;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
@@ -541,17 +542,19 @@ public interface JdbcSettings extends C3p0Settings, AgroalSettings, HikariCPSett
/**
* Whether access to JDBC {@linkplain java.sql.DatabaseMetaData metadata} is allowed during bootstrap.
*
- * Typically, Hibernate accesses this metadata to understand the capabilities of the underlying
- * database to help minimize needed configuration. Disabling this access means that only explicit
- * settings are used. At a minimum, the Dialect to use must be specified using either the {@value #DIALECT}
- * or {@value JdbcSettings#JAKARTA_HBM2DDL_DB_NAME} setting. When the Dialect to use is specified in
- * this manner it is generally a good idea to specify the
- * {@linkplain JdbcSettings#JAKARTA_HBM2DDL_DB_VERSION database version} as well - Dialects use the
- * version to configure themselves.
+ * Allowable options are defined by {@linkplain JdbcMetadataOnBoot}. For configuration, any of the
+ * following forms are accepted:
+ * - an instance of {@linkplain JdbcMetadataOnBoot}
+ * - case-insensitive {@linkplain JdbcMetadataOnBoot} option name
+ * - for legacy purposes, {@code true} or {@code false} -
+ * {@code true} is mapped to {@linkplain JdbcMetadataOnBoot#ALLOW} and
+ * {@code false} is mapped to {@linkplain JdbcMetadataOnBoot#DISALLOW}
+ *
+ *
*
- * @apiNote The specified Dialect may also provide defaults into the "explicit" settings.
+ * @settingDefault {@code allow}
*
- * @settingDefault {@code true}
+ * @see JdbcMetadataOnBoot
*
* @since 6.5
*/
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/JdbcMetadataOnBoot.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/JdbcMetadataOnBoot.java
new file mode 100644
index 000000000000..0b67fa2a95a4
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/JdbcMetadataOnBoot.java
@@ -0,0 +1,46 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.engine.jdbc.env;
+
+import org.hibernate.cfg.JdbcSettings;
+
+/**
+ * Whether access to {@linkplain java.sql.DatabaseMetaData JDBC metadata} is allowed during bootstrap.
+ * Typically, Hibernate accesses this metadata to understand the capabilities of the underlying
+ * database to help minimize needed configuration.
+ *
+ * @apiNote The default value is {@linkplain #ALLOW}.
+ *
+ * @see JdbcSettings#ALLOW_METADATA_ON_BOOT
+ *
+ * @author Steve Ebersole
+ */
+public enum JdbcMetadataOnBoot {
+ /**
+ * Access to the {@linkplain java.sql.DatabaseMetaData JDBC metadata} is disallowed.
+ * At a bare minimum, this requires specifying the {@linkplain JdbcSettings#DIALECT dialect}
+ * or {@linkplain JdbcSettings#JAKARTA_HBM2DDL_DB_NAME database} being used.
+ * Specifying the {@linkplain JdbcSettings#JAKARTA_HBM2DDL_DB_VERSION database version} is
+ * recommended as well.
+ *
+ * @apiNote The specified Dialect may also provide defaults into the "explicit" settings.
+ */
+ DISALLOW,
+ /**
+ * Access to the {@linkplain java.sql.DatabaseMetaData JDBC metadata} is allowed.
+ *
+ * @apiNote This is the default.
+ * @implNote When errors occur accessing the {@linkplain java.sql.DatabaseMetaData JDBC metadata},
+ * implicit values will be used as needed.
+ */
+ ALLOW,
+ /**
+ * Access to the {@linkplain java.sql.DatabaseMetaData JDBC metadata} is required.
+ *
+ * @implNote Functions like {@linkplain #ALLOW}, except that errors which occur when accessing the
+ * JDBC metadata will be propagated back to the application.
+ */
+ REQUIRE
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java
index 2f92bf2e14f8..e511970717c4 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java
@@ -4,13 +4,7 @@
*/
package org.hibernate.engine.jdbc.env.internal;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-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;
@@ -24,6 +18,7 @@
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
+import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl;
import org.hibernate.engine.jdbc.internal.JdbcServicesImpl;
@@ -44,8 +39,13 @@
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.stat.spi.StatisticsImplementor;
-
-import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
import static java.lang.Integer.parseInt;
import static org.hibernate.cfg.AvailableSettings.CONNECTION_HANDLING;
@@ -62,8 +62,9 @@
import static org.hibernate.cfg.JdbcSettings.DIALECT;
import static org.hibernate.cfg.JdbcSettings.DIALECT_DB_VERSION;
import static org.hibernate.cfg.JdbcSettings.JAKARTA_HBM2DDL_DB_VERSION;
-import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
import static org.hibernate.context.spi.MultiTenancy.isMultiTenancyEnabled;
+import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
+import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER;
import static org.hibernate.internal.log.ConnectionInfoLogger.CONNECTION_INFO_LOGGER;
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues;
@@ -86,7 +87,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator getServiceInitiated() {
@@ -137,8 +138,10 @@ private JdbcEnvironment getJdbcEnvironment(
final JdbcEnvironment jdbcEnvironment;
final DatabaseConnectionInfo databaseConnectionInfo;
- if ( allowJdbcMetadataAccess( configurationValues ) ) {
+ final JdbcMetadataOnBoot jdbcMetadataAccess = jdbcMetadataAccess( configurationValues );
+ if ( jdbcMetadataAccess != JdbcMetadataOnBoot.DISALLOW ) {
jdbcEnvironment = getJdbcEnvironmentUsingJdbcMetadata(
+ jdbcMetadataAccess,
configurationValues,
registry,
dialectFactory,
@@ -241,20 +244,37 @@ protected JdbcEnvironmentImpl getJdbcEnvironmentWithExplicitConfiguration(
*
* @see JdbcSettings#ALLOW_METADATA_ON_BOOT
*/
- public static boolean allowJdbcMetadataAccess(Map configurationValues) {
- final Boolean allow = getBooleanWrapper( ALLOW_METADATA_ON_BOOT, configurationValues, null );
- if ( allow != null ) {
- return allow;
+ public static JdbcMetadataOnBoot jdbcMetadataAccess(Map configurationValues) {
+ final Object setting = configurationValues.get( ALLOW_METADATA_ON_BOOT );
+ if ( setting != null ) {
+ // might be any number of forms....
+ if ( setting instanceof JdbcMetadataOnBoot asEnum ) {
+ return asEnum;
+ }
+
+ if ( setting instanceof String asString ) {
+ if ( asString.equalsIgnoreCase( "true" ) ) {
+ return JdbcMetadataOnBoot.ALLOW;
+ }
+ if ( asString.equalsIgnoreCase( "false" ) ) {
+ return JdbcMetadataOnBoot.DISALLOW;
+ }
+ return JdbcMetadataOnBoot.valueOf( asString.toUpperCase( Locale.ROOT ) );
+ }
+
+ if ( setting instanceof Boolean asBoolean ) {
+ return asBoolean ? JdbcMetadataOnBoot.ALLOW : JdbcMetadataOnBoot.DISALLOW;
+ }
}
final Boolean use = getBooleanWrapper( USE_JDBC_METADATA_DEFAULTS, configurationValues, null );
if ( use != null ) {
DEPRECATION_LOGGER.deprecatedSetting( USE_JDBC_METADATA_DEFAULTS, ALLOW_METADATA_ON_BOOT );
- return use;
+ return use ? JdbcMetadataOnBoot.ALLOW : JdbcMetadataOnBoot.DISALLOW;
}
// allow by default
- return true;
+ return JdbcMetadataOnBoot.ALLOW;
}
private static String getExplicitDatabaseVersion(
@@ -323,6 +343,7 @@ private static String getExplicitDatabaseName(Map configurationV
// Used by Hibernate Reactive
protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
+ JdbcMetadataOnBoot jdbcMetadataAccess,
Map configurationValues,
ServiceRegistryImplementor registry,
DialectFactory dialectFactory, String explicitDatabaseName,
@@ -345,7 +366,7 @@ protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
return temporaryJdbcSessionOwner.transactionCoordinator.createIsolationDelegate().delegateWork(
new AbstractReturningWork<>() {
@Override
- public JdbcEnvironmentImpl execute(Connection connection) {
+ public JdbcEnvironmentImpl execute(Connection connection) throws SQLException {
try {
final var metadata = connection.getMetaData();
logDatabaseAndDriver( metadata );
@@ -380,7 +401,12 @@ public JdbcEnvironmentImpl execute(Connection connection) {
);
}
catch (SQLException e) {
- JDBC_LOGGER.unableToObtainConnectionMetadata( e );
+ if ( jdbcMetadataAccess == JdbcMetadataOnBoot.REQUIRE ) {
+ throw e;
+ }
+ else {
+ JDBC_LOGGER.unableToObtainConnectionMetadata( e );
+ }
}
// accessing the JDBC metadata failed
@@ -411,7 +437,12 @@ private int databaseMicroVersion(DatabaseMetaData metadata) throws SQLException
);
}
catch ( Exception e ) {
- JDBC_LOGGER.unableToObtainConnectionToQueryMetadata( e );
+ if ( jdbcMetadataAccess == JdbcMetadataOnBoot.REQUIRE ) {
+ throw new HibernateException( "Unable to access JDBC metadata", e );
+ }
+ else {
+ JDBC_LOGGER.unableToObtainConnectionToQueryMetadata( e );
+ }
}
finally {
//noinspection resource
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/FormsTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/FormsTests.java
new file mode 100644
index 000000000000..e46128a3071a
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/FormsTests.java
@@ -0,0 +1,57 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.orm.test.boot.database.metadata;
+
+import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
+import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
+import org.junit.jupiter.api.Test;
+
+import java.util.Locale;
+import java.util.Map;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hibernate.cfg.JdbcSettings.ALLOW_METADATA_ON_BOOT;
+import static org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot.ALLOW;
+import static org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot.DISALLOW;
+import static org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot.REQUIRE;
+import static org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.jdbcMetadataAccess;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Steve Ebersole
+ */
+public class FormsTests {
+ @Test
+ void testConfigForms() {
+ check( ALLOW_METADATA_ON_BOOT, ALLOW, ALLOW );
+ check( ALLOW_METADATA_ON_BOOT, ALLOW.name(), ALLOW );
+ check( ALLOW_METADATA_ON_BOOT, ALLOW.name().toLowerCase( Locale.ROOT ), ALLOW );
+ check( ALLOW_METADATA_ON_BOOT, true, ALLOW );
+ check( ALLOW_METADATA_ON_BOOT, TRUE, ALLOW );
+ check( ALLOW_METADATA_ON_BOOT, "true", ALLOW );
+
+ check( ALLOW_METADATA_ON_BOOT, DISALLOW, DISALLOW );
+ check( ALLOW_METADATA_ON_BOOT, DISALLOW.name(), DISALLOW );
+ check( ALLOW_METADATA_ON_BOOT, DISALLOW.name().toLowerCase( Locale.ROOT ), DISALLOW );
+ check( ALLOW_METADATA_ON_BOOT, false, DISALLOW );
+ check( ALLOW_METADATA_ON_BOOT, FALSE, DISALLOW );
+ check( ALLOW_METADATA_ON_BOOT, "false", DISALLOW );
+
+ check( ALLOW_METADATA_ON_BOOT, REQUIRE, REQUIRE );
+ check( ALLOW_METADATA_ON_BOOT, REQUIRE.name(), REQUIRE );
+ check( ALLOW_METADATA_ON_BOOT, REQUIRE.name().toLowerCase( Locale.ROOT ), REQUIRE );
+
+ check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, TRUE, ALLOW );
+ check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, true, ALLOW );
+
+ check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, FALSE, DISALLOW );
+ check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, false, DISALLOW );
+ }
+
+ private void check(String configName, Object setting, JdbcMetadataOnBoot expected) {
+ assertEquals( expected, jdbcMetadataAccess( Map.of( configName, setting ) ) );
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java
index f03c7a24d520..3ce22d692d5b 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java
@@ -37,6 +37,7 @@
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfoSource;
+import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.service.spi.ServiceException;
import org.hibernate.service.spi.ServiceRegistryImplementor;
@@ -45,6 +46,7 @@
import org.hibernate.testing.logger.Triggerable;
import org.hibernate.testing.orm.junit.DialectContext;
import org.hibernate.testing.orm.junit.Jira;
+import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.logger.LoggerInspectionExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -54,6 +56,10 @@
import org.junit.jupiter.params.provider.MethodSource;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.jupiter.api.Assertions.fail;
+
/**
* @author Steve Ebersole
*/
@@ -205,6 +211,24 @@ void testAccessDisabledNoDialectNorProductName() {
}
}
+ @Test
+ @Jira("https://hibernate.atlassian.net/browse/HHH-18286")
+ @RequiresDialect(value = H2Dialect.class, comment = "Unit test - limit to default job")
+ void testDontIgnoreMetadataAccessFailureWhenConnectionCantBeObtained() {
+ StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
+ registryBuilder.clearSettings();
+
+ registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, JdbcMetadataOnBoot.REQUIRE );
+
+ try (StandardServiceRegistry registry = registryBuilder.build()) {
+ assertThatExceptionOfType( ServiceException.class )
+ .isThrownBy( () -> registry.getService( JdbcEnvironment.class ) )
+ .havingCause()
+ .isInstanceOf( HibernateException.class )
+ .withMessage( "Unable to access JDBC metadata" );
+ }
+ }
+
@Test
void testDetermineDatabaseVersion() {
final Dialect metadataAccessDisabledDialect;