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:

* - * @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;