Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -541,17 +542,19 @@ public interface JdbcSettings extends C3p0Settings, AgroalSettings, HikariCPSett
/**
* 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.
* Allowable options are defined by {@linkplain JdbcMetadataOnBoot}. For configuration, any of the
* following forms are accepted: <ul>
* <li>an instance of {@linkplain JdbcMetadataOnBoot}</li>
* <li>case-insensitive {@linkplain JdbcMetadataOnBoot} option name</li>
* <li>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}
* </li>
* </ul>
*
* @apiNote The specified Dialect may also provide defaults into the "explicit" settings.
* @settingDefault {@code allow}
*
* @settingDefault {@code true}
* @see JdbcMetadataOnBoot
*
* @since 6.5
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -86,7 +87,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
* {@value org.hibernate.cfg.JdbcSettings#ALLOW_METADATA_ON_BOOT}.
*/
@Deprecated(since="6", forRemoval = true)
private static final String USE_JDBC_METADATA_DEFAULTS = "hibernate.temp.use_jdbc_metadata_defaults";
public static final String USE_JDBC_METADATA_DEFAULTS = "hibernate.temp.use_jdbc_metadata_defaults";

@Override
public Class<JdbcEnvironment> getServiceInitiated() {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -241,20 +244,37 @@ protected JdbcEnvironmentImpl getJdbcEnvironmentWithExplicitConfiguration(
*
* @see JdbcSettings#ALLOW_METADATA_ON_BOOT
*/
public static boolean allowJdbcMetadataAccess(Map<String, Object> configurationValues) {
final Boolean allow = getBooleanWrapper( ALLOW_METADATA_ON_BOOT, configurationValues, null );
if ( allow != null ) {
return allow;
public static JdbcMetadataOnBoot jdbcMetadataAccess(Map<String, Object> 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(
Expand Down Expand Up @@ -323,6 +343,7 @@ private static String getExplicitDatabaseName(Map<String, Object> configurationV

// Used by Hibernate Reactive
protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
JdbcMetadataOnBoot jdbcMetadataAccess,
Map<String, Object> configurationValues,
ServiceRegistryImplementor registry,
DialectFactory dialectFactory, String explicitDatabaseName,
Expand All @@ -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 );
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ) ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down