HHH-17269 - Add hibernate.boot.allow_jdbc_metadata_access

This commit is contained in:
Steve Ebersole 2024-03-06 15:04:10 -06:00
parent 39cd03d1a5
commit ab01984807
3 changed files with 136 additions and 10 deletions

View File

@ -471,6 +471,25 @@ public interface JdbcSettings extends C3p0Settings, ProxoolSettings {
*/ */
String CONNECTION_HANDLING = "hibernate.connection.handling_mode"; String CONNECTION_HANDLING = "hibernate.connection.handling_mode";
/**
* Whether access to JDBC {@linkplain java.sql.DatabaseMetaData metadata} is allowed during bootstrap.
* <p/>
* 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 // Deprecated Hibernate settings

View File

@ -14,6 +14,7 @@ import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.boot.registry.StandardServiceInitiator;
import org.hibernate.cfg.JdbcSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.batch.spi.BatchBuilder; 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.JAKARTA_HBM2DDL_DB_NAME;
import static org.hibernate.cfg.AvailableSettings.JTA_TRACK_BY_THREAD; import static org.hibernate.cfg.AvailableSettings.JTA_TRACK_BY_THREAD;
import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION; 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.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT;
import static org.hibernate.cfg.JdbcSettings.DIALECT; import static org.hibernate.cfg.JdbcSettings.DIALECT;
import static org.hibernate.cfg.JdbcSettings.DIALECT_DB_VERSION; 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.NullnessHelper.coalesceSuppliedValues;
import static org.hibernate.internal.util.StringHelper.isNotEmpty; 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.getBoolean;
import static org.hibernate.internal.util.config.ConfigurationHelper.getBooleanWrapper;
import static org.hibernate.internal.util.config.ConfigurationHelper.getInteger; import static org.hibernate.internal.util.config.ConfigurationHelper.getInteger;
/** /**
@ -115,7 +118,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
} }
} }
if ( useJdbcMetadata( configurationValues ) ) { if ( allowJdbcMetadataAccess( configurationValues ) ) {
return getJdbcEnvironmentUsingJdbcMetadata( return getJdbcEnvironmentUsingJdbcMetadata(
configurationValues, configurationValues,
registry, registry,
@ -179,15 +182,28 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
return new JdbcEnvironmentImpl( registry, dialect ); return new JdbcEnvironmentImpl( registry, dialect );
} }
// 'hibernate.temp.use_jdbc_metadata_defaults' is a temporary magic value. /**
// The need for it is intended to be alleviated with future development, thus it is * Determine whether we can access JDBC {@linkplain DatabaseMetaData metadata} based on
// not defined as an Environment constant... * the {@value JdbcSettings#ALLOW_METADATA_ON_BOOT} setting. The default is to allow access.
// *
// it is used to control whether we should consult the JDBC metadata to determine * @implNote Currently also looks for the deprecated {@value JdbcEnvironmentInitiator#USE_JDBC_METADATA_DEFAULTS} setting as a fallback.
// certain default values; it is useful to *not* do this when the database *
// may not be available (mainly in tools usage). * @see JdbcSettings#ALLOW_METADATA_ON_BOOT
private static boolean useJdbcMetadata(Map<String, Object> configurationValues) { */
return getBoolean(USE_JDBC_METADATA_DEFAULTS, configurationValues, true ); private static boolean allowJdbcMetadataAccess(Map<String, Object> 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( private static String getExplicitDatabaseVersion(

View File

@ -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" );
}
}
}