From ab01984807a1927d68f19ac082913f2a84d1c751 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 6 Mar 2024 15:04:10 -0600 Subject: [PATCH] HHH-17269 - Add hibernate.boot.allow_jdbc_metadata_access --- .../java/org/hibernate/cfg/JdbcSettings.java | 19 ++++ .../internal/JdbcEnvironmentInitiator.java | 36 ++++++-- .../metadata/MetadataAccessTests.java | 91 +++++++++++++++++++ 3 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java 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 b60ee49560..9e15a64ddc 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java @@ -471,6 +471,25 @@ public interface JdbcSettings extends C3p0Settings, ProxoolSettings { */ String CONNECTION_HANDLING = "hibernate.connection.handling_mode"; + /** + * 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. + * + * @apiNote The specified Dialect may also provide defaults into the "explicit" settings. + * + * @settingDefault {@code true} + * + * @since 6.5 + */ + String ALLOW_METADATA_ON_BOOT = "hibernate.boot.allow_jdbc_metadata_access"; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Deprecated Hibernate settings 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 afe804a291..ac6d814599 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 @@ -14,6 +14,7 @@ import java.util.Map; import java.util.StringTokenizer; import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.cfg.JdbcSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.batch.spi.BatchBuilder; @@ -55,6 +56,7 @@ import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DB_MINOR_VERSI import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DB_NAME; import static org.hibernate.cfg.AvailableSettings.JTA_TRACK_BY_THREAD; import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION; +import static org.hibernate.cfg.JdbcSettings.ALLOW_METADATA_ON_BOOT; import static org.hibernate.cfg.JdbcSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT; import static org.hibernate.cfg.JdbcSettings.DIALECT; import static org.hibernate.cfg.JdbcSettings.DIALECT_DB_VERSION; @@ -65,6 +67,7 @@ import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; +import static org.hibernate.internal.util.config.ConfigurationHelper.getBooleanWrapper; import static org.hibernate.internal.util.config.ConfigurationHelper.getInteger; /** @@ -115,7 +118,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator configurationValues) { - return getBoolean(USE_JDBC_METADATA_DEFAULTS, configurationValues, true ); + /** + * Determine whether we can access JDBC {@linkplain DatabaseMetaData metadata} based on + * the {@value JdbcSettings#ALLOW_METADATA_ON_BOOT} setting. The default is to allow access. + * + * @implNote Currently also looks for the deprecated {@value JdbcEnvironmentInitiator#USE_JDBC_METADATA_DEFAULTS} setting as a fallback. + * + * @see JdbcSettings#ALLOW_METADATA_ON_BOOT + */ + private static boolean allowJdbcMetadataAccess(Map configurationValues) { + final Boolean allow = getBooleanWrapper( ALLOW_METADATA_ON_BOOT, configurationValues, null ); + if ( allow != null ) { + return allow; + } + + 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; + } + + // allow by default + return true; } private static String getExplicitDatabaseVersion( 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 new file mode 100644 index 0000000000..11db3d4c04 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.boot.database.metadata; + +import org.hibernate.HibernateException; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.JdbcSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.service.spi.ServiceException; + +import org.hibernate.testing.env.TestingDatabaseInfo; +import org.hibernate.testing.orm.junit.Jira; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +@Jira( "https://hibernate.atlassian.net/browse/HHH-17269" ) +public class MetadataAccessTests { + + @Test + void testAccessAllowed() { + final StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder(); + registryBuilder.clearSettings(); + + // allow access to the jdbc metadata + registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, true ); + + // configure the values needed to connect to a H2 database + registryBuilder.applySetting( AvailableSettings.JAKARTA_JDBC_DRIVER, TestingDatabaseInfo.DRIVER ); + registryBuilder.applySetting( AvailableSettings.JAKARTA_JDBC_URL, TestingDatabaseInfo.URL ); + registryBuilder.applySetting( AvailableSettings.JAKARTA_JDBC_USER, TestingDatabaseInfo.USER ); + registryBuilder.applySetting( AvailableSettings.JAKARTA_JDBC_PASSWORD, TestingDatabaseInfo.PASS ); + + // make certain there is no explicit dialect configured + assertThat( registryBuilder.getSettings() ).doesNotContainKey( JdbcSettings.DIALECT ); + + try (StandardServiceRegistry registry = registryBuilder.build()) { + final JdbcEnvironment jdbcEnvironment = registry.getService( JdbcEnvironment.class ); + final Dialect dialect = jdbcEnvironment.getDialect(); + assertThat( dialect ).isNotNull(); + assertThat( dialect ).isInstanceOf( H2Dialect.class ); + } + } + + @Test + void testAccessDisabledExplicitDialect() { + final StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder(); + registryBuilder.clearSettings(); + + registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, false ); + registryBuilder.applySetting( JdbcSettings.DIALECT, "org.hibernate.dialect.OracleDialect" ); + + try (StandardServiceRegistry registry = registryBuilder.build()) { + final JdbcEnvironment jdbcEnvironment = registry.getService( JdbcEnvironment.class ); + final Dialect dialect = jdbcEnvironment.getDialect(); + assertThat( dialect ).isInstanceOf( OracleDialect.class ); + } + } + + @Test + void testAccessDisabledNoDialect() { + final StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder(); + registryBuilder.clearSettings(); + assertThat( registryBuilder.getSettings() ).doesNotContainKey( JdbcSettings.DIALECT ); + + registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, false ); + try (StandardServiceRegistry registry = registryBuilder.build()) { + final JdbcEnvironment jdbcEnvironment = registry.getService( JdbcEnvironment.class ); + final Dialect dialect = jdbcEnvironment.getDialect(); + fail( "Should fail to boot - " + dialect ); + } + catch (ServiceException expected) { + assertThat( expected.getCause() ).isInstanceOf( HibernateException.class ); + final HibernateException cause = (HibernateException) expected.getCause(); + assertThat( cause.getMessage() ).startsWith( "Unable to determine Dialect without JDBC metadata" ); + } + } +}