Skip to content

Commit b57180a

Browse files
committed
HHH-18286 - Add option to fail bootstrapping on failure to access JDBC DatabaseMetadata
1 parent e80b996 commit b57180a

File tree

5 files changed

+191
-30
lines changed

5 files changed

+191
-30
lines changed

hibernate-core/src/main/java/org/hibernate/cfg/JdbcSettings.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import org.hibernate.Incubating;
1010
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
11+
import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
1112
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
1213
import org.hibernate.query.Query;
1314
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
@@ -541,17 +542,19 @@ public interface JdbcSettings extends C3p0Settings, AgroalSettings, HikariCPSett
541542
/**
542543
* Whether access to JDBC {@linkplain java.sql.DatabaseMetaData metadata} is allowed during bootstrap.
543544
* <p/>
544-
* Typically, Hibernate accesses this metadata to understand the capabilities of the underlying
545-
* database to help minimize needed configuration. Disabling this access means that only explicit
546-
* settings are used. At a minimum, the Dialect to use must be specified using either the {@value #DIALECT}
547-
* or {@value JdbcSettings#JAKARTA_HBM2DDL_DB_NAME} setting. When the Dialect to use is specified in
548-
* this manner it is generally a good idea to specify the
549-
* {@linkplain JdbcSettings#JAKARTA_HBM2DDL_DB_VERSION database version} as well - Dialects use the
550-
* version to configure themselves.
545+
* Allowable options are defined by {@linkplain JdbcMetadataOnBoot}. For configuration, any of the
546+
* following forms are accepted: <ul>
547+
* <li>an instance of {@linkplain JdbcMetadataOnBoot}</li>
548+
* <li>case-insensitive {@linkplain JdbcMetadataOnBoot} option name</li>
549+
* <li>for legacy purposes, {@code true} or {@code false} -
550+
* {@code true} is mapped to {@linkplain JdbcMetadataOnBoot#ALLOW} and
551+
* {@code false} is mapped to {@linkplain JdbcMetadataOnBoot#DISALLOW}
552+
* </li>
553+
* </ul>
551554
*
552-
* @apiNote The specified Dialect may also provide defaults into the "explicit" settings.
555+
* @settingDefault {@code allow}
553556
*
554-
* @settingDefault {@code true}
557+
* @see JdbcMetadataOnBoot
555558
*
556559
* @since 6.5
557560
*/
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.engine.jdbc.env;
6+
7+
import org.hibernate.cfg.JdbcSettings;
8+
9+
/**
10+
* Whether access to {@linkplain java.sql.DatabaseMetaData JDBC metadata} is allowed during bootstrap.
11+
* Typically, Hibernate accesses this metadata to understand the capabilities of the underlying
12+
* database to help minimize needed configuration.
13+
*
14+
* @apiNote The default value is {@linkplain #ALLOW}.
15+
*
16+
* @see JdbcSettings#ALLOW_METADATA_ON_BOOT
17+
*
18+
* @author Steve Ebersole
19+
*/
20+
public enum JdbcMetadataOnBoot {
21+
/**
22+
* Access to the {@linkplain java.sql.DatabaseMetaData JDBC metadata} is disallowed.
23+
* At a bare minimum, this requires specifying the {@linkplain JdbcSettings#DIALECT dialect}
24+
* or {@linkplain JdbcSettings#JAKARTA_HBM2DDL_DB_NAME database} being used.
25+
* Specifying the {@linkplain JdbcSettings#JAKARTA_HBM2DDL_DB_VERSION database version} is
26+
* recommended as well.
27+
*
28+
* @apiNote The specified Dialect may also provide defaults into the "explicit" settings.
29+
*/
30+
DISALLOW,
31+
/**
32+
* Access to the {@linkplain java.sql.DatabaseMetaData JDBC metadata} is allowed.
33+
*
34+
* @apiNote This is the default.
35+
* @implNote When errors occur accessing the {@linkplain java.sql.DatabaseMetaData JDBC metadata},
36+
* implicit values will be used as needed.
37+
*/
38+
ALLOW,
39+
/**
40+
* Access to the {@linkplain java.sql.DatabaseMetaData JDBC metadata} is required.
41+
*
42+
* @implNote Functions like {@linkplain #ALLOW}, except that errors which occur when accessing the
43+
* JDBC metadata will be propagated back to the application.
44+
*/
45+
REQUIRE
46+
}

hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@
44
*/
55
package org.hibernate.engine.jdbc.env.internal;
66

7-
import java.sql.Connection;
8-
import java.sql.DatabaseMetaData;
9-
import java.sql.SQLException;
10-
import java.util.Collections;
11-
import java.util.Map;
12-
import java.util.StringTokenizer;
13-
7+
import org.hibernate.HibernateException;
148
import org.hibernate.boot.registry.StandardServiceInitiator;
159
import org.hibernate.cfg.JdbcSettings;
1610
import org.hibernate.dialect.DatabaseVersion;
@@ -24,6 +18,7 @@
2418
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
2519
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
2620
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
21+
import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
2722
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
2823
import org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl;
2924
import org.hibernate.engine.jdbc.internal.JdbcServicesImpl;
@@ -44,8 +39,13 @@
4439
import org.hibernate.service.spi.ServiceRegistryImplementor;
4540
import org.hibernate.stat.spi.StatisticsImplementor;
4641

47-
48-
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER;
42+
import java.sql.Connection;
43+
import java.sql.DatabaseMetaData;
44+
import java.sql.SQLException;
45+
import java.util.Collections;
46+
import java.util.Locale;
47+
import java.util.Map;
48+
import java.util.StringTokenizer;
4949

5050
import static java.lang.Integer.parseInt;
5151
import static org.hibernate.cfg.AvailableSettings.CONNECTION_HANDLING;
@@ -62,8 +62,9 @@
6262
import static org.hibernate.cfg.JdbcSettings.DIALECT;
6363
import static org.hibernate.cfg.JdbcSettings.DIALECT_DB_VERSION;
6464
import static org.hibernate.cfg.JdbcSettings.JAKARTA_HBM2DDL_DB_VERSION;
65-
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
6665
import static org.hibernate.context.spi.MultiTenancy.isMultiTenancyEnabled;
66+
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
67+
import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_LOGGER;
6768
import static org.hibernate.internal.log.ConnectionInfoLogger.CONNECTION_INFO_LOGGER;
6869
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
6970
import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues;
@@ -86,7 +87,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
8687
* {@value org.hibernate.cfg.JdbcSettings#ALLOW_METADATA_ON_BOOT}.
8788
*/
8889
@Deprecated(since="6", forRemoval = true)
89-
private static final String USE_JDBC_METADATA_DEFAULTS = "hibernate.temp.use_jdbc_metadata_defaults";
90+
public static final String USE_JDBC_METADATA_DEFAULTS = "hibernate.temp.use_jdbc_metadata_defaults";
9091

9192
@Override
9293
public Class<JdbcEnvironment> getServiceInitiated() {
@@ -137,8 +138,10 @@ private JdbcEnvironment getJdbcEnvironment(
137138

138139
final JdbcEnvironment jdbcEnvironment;
139140
final DatabaseConnectionInfo databaseConnectionInfo;
140-
if ( allowJdbcMetadataAccess( configurationValues ) ) {
141+
final JdbcMetadataOnBoot jdbcMetadataAccess = jdbcMetadataAccess( configurationValues );
142+
if ( jdbcMetadataAccess != JdbcMetadataOnBoot.DISALLOW ) {
141143
jdbcEnvironment = getJdbcEnvironmentUsingJdbcMetadata(
144+
jdbcMetadataAccess,
142145
configurationValues,
143146
registry,
144147
dialectFactory,
@@ -241,20 +244,37 @@ protected JdbcEnvironmentImpl getJdbcEnvironmentWithExplicitConfiguration(
241244
*
242245
* @see JdbcSettings#ALLOW_METADATA_ON_BOOT
243246
*/
244-
public static boolean allowJdbcMetadataAccess(Map<String, Object> configurationValues) {
245-
final Boolean allow = getBooleanWrapper( ALLOW_METADATA_ON_BOOT, configurationValues, null );
246-
if ( allow != null ) {
247-
return allow;
247+
public static JdbcMetadataOnBoot jdbcMetadataAccess(Map<String, Object> configurationValues) {
248+
final Object setting = configurationValues.get( ALLOW_METADATA_ON_BOOT );
249+
if ( setting != null ) {
250+
// might be any number of forms....
251+
if ( setting instanceof JdbcMetadataOnBoot asEnum ) {
252+
return asEnum;
253+
}
254+
255+
if ( setting instanceof String asString ) {
256+
if ( asString.equalsIgnoreCase( "true" ) ) {
257+
return JdbcMetadataOnBoot.ALLOW;
258+
}
259+
if ( asString.equalsIgnoreCase( "false" ) ) {
260+
return JdbcMetadataOnBoot.DISALLOW;
261+
}
262+
return JdbcMetadataOnBoot.valueOf( asString.toUpperCase( Locale.ROOT ) );
263+
}
264+
265+
if ( setting instanceof Boolean asBoolean ) {
266+
return asBoolean ? JdbcMetadataOnBoot.ALLOW : JdbcMetadataOnBoot.DISALLOW;
267+
}
248268
}
249269

250270
final Boolean use = getBooleanWrapper( USE_JDBC_METADATA_DEFAULTS, configurationValues, null );
251271
if ( use != null ) {
252272
DEPRECATION_LOGGER.deprecatedSetting( USE_JDBC_METADATA_DEFAULTS, ALLOW_METADATA_ON_BOOT );
253-
return use;
273+
return use ? JdbcMetadataOnBoot.ALLOW : JdbcMetadataOnBoot.DISALLOW;
254274
}
255275

256276
// allow by default
257-
return true;
277+
return JdbcMetadataOnBoot.ALLOW;
258278
}
259279

260280
private static String getExplicitDatabaseVersion(
@@ -323,6 +343,7 @@ private static String getExplicitDatabaseName(Map<String, Object> configurationV
323343

324344
// Used by Hibernate Reactive
325345
protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
346+
JdbcMetadataOnBoot jdbcMetadataAccess,
326347
Map<String, Object> configurationValues,
327348
ServiceRegistryImplementor registry,
328349
DialectFactory dialectFactory, String explicitDatabaseName,
@@ -345,7 +366,7 @@ protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
345366
return temporaryJdbcSessionOwner.transactionCoordinator.createIsolationDelegate().delegateWork(
346367
new AbstractReturningWork<>() {
347368
@Override
348-
public JdbcEnvironmentImpl execute(Connection connection) {
369+
public JdbcEnvironmentImpl execute(Connection connection) throws SQLException {
349370
try {
350371
final var metadata = connection.getMetaData();
351372
logDatabaseAndDriver( metadata );
@@ -380,7 +401,12 @@ public JdbcEnvironmentImpl execute(Connection connection) {
380401
);
381402
}
382403
catch (SQLException e) {
383-
JDBC_LOGGER.unableToObtainConnectionMetadata( e );
404+
if ( jdbcMetadataAccess == JdbcMetadataOnBoot.REQUIRE ) {
405+
throw e;
406+
}
407+
else {
408+
JDBC_LOGGER.unableToObtainConnectionMetadata( e );
409+
}
384410
}
385411

386412
// accessing the JDBC metadata failed
@@ -411,7 +437,12 @@ private int databaseMicroVersion(DatabaseMetaData metadata) throws SQLException
411437
);
412438
}
413439
catch ( Exception e ) {
414-
JDBC_LOGGER.unableToObtainConnectionToQueryMetadata( e );
440+
if ( jdbcMetadataAccess == JdbcMetadataOnBoot.REQUIRE ) {
441+
throw new HibernateException( "Unable to access JDBC metadata", e );
442+
}
443+
else {
444+
JDBC_LOGGER.unableToObtainConnectionToQueryMetadata( e );
445+
}
415446
}
416447
finally {
417448
//noinspection resource
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.boot.database.metadata;
6+
7+
import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
8+
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
9+
import org.junit.jupiter.api.Test;
10+
11+
import java.util.Locale;
12+
import java.util.Map;
13+
14+
import static java.lang.Boolean.FALSE;
15+
import static java.lang.Boolean.TRUE;
16+
import static org.hibernate.cfg.JdbcSettings.ALLOW_METADATA_ON_BOOT;
17+
import static org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot.ALLOW;
18+
import static org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot.DISALLOW;
19+
import static org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot.REQUIRE;
20+
import static org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.jdbcMetadataAccess;
21+
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
23+
/**
24+
* @author Steve Ebersole
25+
*/
26+
public class FormsTests {
27+
@Test
28+
void testConfigForms() {
29+
check( ALLOW_METADATA_ON_BOOT, ALLOW, ALLOW );
30+
check( ALLOW_METADATA_ON_BOOT, ALLOW.name(), ALLOW );
31+
check( ALLOW_METADATA_ON_BOOT, ALLOW.name().toLowerCase( Locale.ROOT ), ALLOW );
32+
check( ALLOW_METADATA_ON_BOOT, true, ALLOW );
33+
check( ALLOW_METADATA_ON_BOOT, TRUE, ALLOW );
34+
check( ALLOW_METADATA_ON_BOOT, "true", ALLOW );
35+
36+
check( ALLOW_METADATA_ON_BOOT, DISALLOW, DISALLOW );
37+
check( ALLOW_METADATA_ON_BOOT, DISALLOW.name(), DISALLOW );
38+
check( ALLOW_METADATA_ON_BOOT, DISALLOW.name().toLowerCase( Locale.ROOT ), DISALLOW );
39+
check( ALLOW_METADATA_ON_BOOT, false, DISALLOW );
40+
check( ALLOW_METADATA_ON_BOOT, FALSE, DISALLOW );
41+
check( ALLOW_METADATA_ON_BOOT, "false", DISALLOW );
42+
43+
check( ALLOW_METADATA_ON_BOOT, REQUIRE, REQUIRE );
44+
check( ALLOW_METADATA_ON_BOOT, REQUIRE.name(), REQUIRE );
45+
check( ALLOW_METADATA_ON_BOOT, REQUIRE.name().toLowerCase( Locale.ROOT ), REQUIRE );
46+
47+
check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, TRUE, ALLOW );
48+
check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, true, ALLOW );
49+
50+
check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, FALSE, DISALLOW );
51+
check( JdbcEnvironmentInitiator.USE_JDBC_METADATA_DEFAULTS, false, DISALLOW );
52+
}
53+
54+
private void check(String configName, Object setting, JdbcMetadataOnBoot expected) {
55+
assertEquals( expected, jdbcMetadataAccess( Map.of( configName, setting ) ) );
56+
}
57+
}

hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/metadata/MetadataAccessTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
3838
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
3939
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfoSource;
40+
import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot;
4041
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
4142
import org.hibernate.service.spi.ServiceException;
4243
import org.hibernate.service.spi.ServiceRegistryImplementor;
@@ -45,6 +46,7 @@
4546
import org.hibernate.testing.logger.Triggerable;
4647
import org.hibernate.testing.orm.junit.DialectContext;
4748
import org.hibernate.testing.orm.junit.Jira;
49+
import org.hibernate.testing.orm.junit.RequiresDialect;
4850
import org.hibernate.testing.orm.logger.LoggerInspectionExtension;
4951
import org.junit.jupiter.api.BeforeEach;
5052
import org.junit.jupiter.api.Test;
@@ -54,6 +56,10 @@
5456
import org.junit.jupiter.params.provider.MethodSource;
5557

5658

59+
import static org.assertj.core.api.Assertions.assertThat;
60+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
61+
import static org.junit.jupiter.api.Assertions.fail;
62+
5763
/**
5864
* @author Steve Ebersole
5965
*/
@@ -205,6 +211,24 @@ void testAccessDisabledNoDialectNorProductName() {
205211
}
206212
}
207213

214+
@Test
215+
@Jira("https://hibernate.atlassian.net/browse/HHH-18286")
216+
@RequiresDialect(value = H2Dialect.class, comment = "Unit test - limit to default job")
217+
void testDontIgnoreMetadataAccessFailureWhenConnectionCantBeObtained() {
218+
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
219+
registryBuilder.clearSettings();
220+
221+
registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, JdbcMetadataOnBoot.REQUIRE );
222+
223+
try (StandardServiceRegistry registry = registryBuilder.build()) {
224+
assertThatExceptionOfType( ServiceException.class )
225+
.isThrownBy( () -> registry.getService( JdbcEnvironment.class ) )
226+
.havingCause()
227+
.isInstanceOf( HibernateException.class )
228+
.withMessage( "Unable to access JDBC metadata" );
229+
}
230+
}
231+
208232
@Test
209233
void testDetermineDatabaseVersion() {
210234
final Dialect metadataAccessDisabledDialect;

0 commit comments

Comments
 (0)