Skip to content

Commit

Permalink
Add quarkus.hibernate-orm.database.version-check.enabled
Browse files Browse the repository at this point in the history
This allows disabling the check on startup if one knows the database
won't be reachable.

It also currently defaults to being disabled when a dialect is set
explicitly, in order to work around problems we have with correctly
detecting the version on some databases that we don't have tests for
(unsupported ones).
  • Loading branch information
yrodiere committed Oct 8, 2024
1 parent 63a1fea commit abdf9fa
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 31 deletions.
15 changes: 12 additions & 3 deletions docs/src/main/asciidoc/hibernate-orm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,20 @@ or implicitly set by the Quarkus build process to a minimum supported version of
Quarkus will try to check this preconfigured version against the actual database version on startup,
leading to a startup failure when the actual version is lower.
This is because Hibernate ORM may generate SQL that is invalid
for versions of the database older than what is configured,
which would lead to runtime exceptions.
This is a safeguard: for versions of the database older than what is configured,
Hibernate ORM may generate SQL that is invalid which would lead to runtime exceptions.
// TODO disable the check by default when offline startup is opted in
// See https://github.com/quarkusio/quarkus/issues/13522
If the database cannot be reached, a warning will be logged but startup will proceed.
You can optionally disable the version check if you know the database won't be reachable on startup
using <<quarkus-hibernate-orm_quarkus-hibernate-orm-database-version-check,`quarkus.hibernate-orm.database.version-check.enabled=false`>>.
// TODO change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
The version is disabled by default when a dialect is set explicitly,
as a workaround for https://github.com/quarkusio/quarkus/issues/42255[#42255]/link:https://github.com/quarkusio/quarkus/issues/43703[#43703].
====

[[hibernate-dialect-other-databases]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ public void configurationDescriptorBuilding(
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion),
Optional.ofNullable(xmlDescriptor.getProperties().getProperty(AvailableSettings.DIALECT)),
getMultiTenancyStrategy(
Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
.getProperties().getProperty("hibernate.multiTenancy"))), //FIXME this property is meaningless in Hibernate ORM 6
Expand Down Expand Up @@ -1103,6 +1104,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion),
persistenceUnitConfig.dialect().dialect(),
multiTenancyStrategy,
hibernateOrmConfig.database().ormCompatibilityVersion(),
persistenceUnitConfig.unsupportedProperties()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
}
RuntimeSettings runtimeSettings = buildRuntimeSettings(persistenceUnitName, recordedState, puConfig);

StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(runtimeSettings,
recordedState, persistenceUnitName);
StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(persistenceUnitName,
recordedState, puConfig, runtimeSettings);

final Object cdiBeanManager = Arc.container().beanManager();
final Object validatorFactory = Arc.container().instance("quarkus-hibernate-validator-factory").get();
Expand Down Expand Up @@ -283,10 +283,10 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde
return runtimeSettingsBuilder.build();
}

private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(RuntimeSettings runtimeSettings, RecordedState rs,
String persistenceUnitName) {
private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(String persistenceUnitName, RecordedState rs,
HibernateOrmRuntimeConfigPersistenceUnit puConfig, RuntimeSettings runtimeSettings) {
PreconfiguredServiceRegistryBuilder serviceRegistryBuilder = new PreconfiguredServiceRegistryBuilder(
persistenceUnitName, rs);
persistenceUnitName, rs, puConfig);

runtimeSettings.getSettings().forEach((key, value) -> {
serviceRegistryBuilder.applySetting(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithName;
import io.smallrye.config.WithParentName;

@ConfigGroup
Expand Down Expand Up @@ -101,6 +102,23 @@ interface HibernateOrmConfigPersistenceUnitDatabase {
@WithConverter(TrimmedStringConverter.class)
Optional<String> defaultSchema();

/**
* Whether Hibernate ORM should check on startup
* that the version of the database matches the version configured on the dialect
* (either the default version, or the one set through `quarkus.datasource.db-version`).
*
* This should be set to `false` if the database is not available on startup.
*
* @asciidoclet
*/
// TODO change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
// TODO disable the check by default when offline startup is opted in
// See https://github.com/quarkusio/quarkus/issues/13522
@WithName("version-check.enabled")
@ConfigDocDefault("`true` if the dialect was set automatically by Quarkus, `false` if it was set explicitly")
Optional<Boolean> versionCheckEnabled();
}

@ConfigGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerProviderInitiator;
import org.hibernate.tool.schema.internal.SchemaManagementToolInitiator;

import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfigPersistenceUnit;
import io.quarkus.hibernate.orm.runtime.cdi.QuarkusManagedBeanRegistryInitiator;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusJndiServiceInitiator;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusJtaPlatformInitiator;
Expand Down Expand Up @@ -65,20 +66,21 @@ public class PreconfiguredServiceRegistryBuilder {
private final Collection<Integrator> integrators;
private final StandardServiceRegistryImpl destroyedRegistry;

public PreconfiguredServiceRegistryBuilder(String puName, RecordedState rs) {
private void checkIsNotReactive(RecordedState rs) {
if (rs.isReactive())
throw new IllegalStateException("Booting a blocking Hibernate ORM serviceregistry on a Reactive RecordedState!");
}

public PreconfiguredServiceRegistryBuilder(String puName, RecordedState rs,
HibernateOrmRuntimeConfigPersistenceUnit puConfig) {
checkIsNotReactive(rs);
this.initiators = buildQuarkusServiceInitiatorList(puName, rs);
this.initiators = buildQuarkusServiceInitiatorList(puName, rs, puConfig);
this.integrators = rs.getIntegrators();
this.destroyedRegistry = (StandardServiceRegistryImpl) rs.getMetadata()
.getMetadataBuildingOptions()
.getServiceRegistry();
}

private void checkIsNotReactive(RecordedState rs) {
if (rs.isReactive())
throw new IllegalStateException("Booting a blocking Hibernate ORM serviceregistry on a Reactive RecordedState!");
}

public PreconfiguredServiceRegistryBuilder applySetting(String settingName, Object value) {
configurationValues.put(settingName, value);
return this;
Expand Down Expand Up @@ -148,7 +150,8 @@ private BootstrapServiceRegistry buildEmptyBootstrapServiceRegistry() {
*
* @return
*/
private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorList(String puName, RecordedState rs) {
private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorList(String puName, RecordedState rs,
HibernateOrmRuntimeConfigPersistenceUnit puConfig) {
final ArrayList<StandardServiceInitiator<?>> serviceInitiators = new ArrayList<StandardServiceInitiator<?>>();

//References to this object need to be injected in both the initiator for BytecodeProvider and for
Expand Down Expand Up @@ -202,7 +205,7 @@ private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorLis
// Custom one: Dialect is injected explicitly
var recordedConfig = rs.getBuildTimeSettings().getSource();
serviceInitiators.add(new QuarkusRuntimeInitDialectFactoryInitiator(puName, rs.isFromPersistenceXml(),
rs.getDialect(), rs.getBuildTimeSettings().getSource()));
rs.getDialect(), rs.getBuildTimeSettings().getSource(), puConfig));

// Default implementation
serviceInitiators.add(BatchBuilderInitiator.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ public class RecordedConfig {
private final Optional<String> dataSource;
private final Optional<String> dbKind;
private final Optional<String> dbVersion;
private final Optional<String> explicitDialect;
private final MultiTenancyStrategy multiTenancyStrategy;
private final Map<String, String> quarkusConfigUnsupportedProperties;
private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;

@RecordableConstructor
public RecordedConfig(Optional<String> dataSource, Optional<String> dbKind,
Optional<String> dbVersion, MultiTenancyStrategy multiTenancyStrategy,
Optional<String> dbVersion, Optional<String> explicitDialect,
MultiTenancyStrategy multiTenancyStrategy,
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
Map<String, String> quarkusConfigUnsupportedProperties) {
Objects.requireNonNull(dataSource);
Expand All @@ -31,6 +33,7 @@ public RecordedConfig(Optional<String> dataSource, Optional<String> dbKind,
this.dataSource = dataSource;
this.dbKind = dbKind;
this.dbVersion = dbVersion;
this.explicitDialect = explicitDialect;
this.multiTenancyStrategy = multiTenancyStrategy;
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
Expand All @@ -48,6 +51,10 @@ public Optional<String> getDbVersion() {
return dbVersion;
}

public Optional<String> getExplicitDialect() {
return explicitDialect;
}

public MultiTenancyStrategy getMultiTenancyStrategy() {
return multiTenancyStrategy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,36 @@ public class QuarkusRuntimeInitDialectFactory implements DialectFactory {
private final Dialect dialect;
private final Optional<String> datasourceName;
private final DatabaseVersion buildTimeDbVersion;
private final boolean versionCheckEnabled;

private boolean triedToRetrieveDbVersion = false;
private Optional<DatabaseVersion> actualDbVersion = Optional.empty();

public QuarkusRuntimeInitDialectFactory(String persistenceUnitName, boolean isFromPersistenceXml, Dialect dialect,
Optional<String> datasourceName, DatabaseVersion buildTimeDbVersion) {
Optional<String> datasourceName, DatabaseVersion buildTimeDbVersion, boolean versionCheckEnabled) {
this.persistenceUnitName = persistenceUnitName;
this.isFromPersistenceXml = isFromPersistenceXml;
this.dialect = dialect;
this.datasourceName = datasourceName;
this.buildTimeDbVersion = buildTimeDbVersion;
this.versionCheckEnabled = versionCheckEnabled;
}

@Override
public Dialect buildDialect(Map<String, Object> configValues, DialectResolutionInfoSource resolutionInfoSource)
throws HibernateException {
if (actualDbVersion.isEmpty()) {
if (versionCheckEnabled && actualDbVersion.isEmpty()) {
this.actualDbVersion = retrieveDbVersion(resolutionInfoSource);
}
return dialect;
}

public void checkActualDbVersion() {
if (!versionCheckEnabled) {
LOG.debugf("Persistence unit %1$s: Skipping database version check; expecting database version to be at least %2$s",
persistenceUnitName, DialectVersions.toString(buildTimeDbVersion));
return;
}
if (!triedToRetrieveDbVersion) {
LOG.warnf("Persistence unit %1$s: Could not retrieve the database version to check it is at least %2$s",
persistenceUnitName, DialectVersions.toString(buildTimeDbVersion));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
import org.hibernate.service.spi.ServiceRegistryImplementor;

import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfigPersistenceUnit;
import io.quarkus.hibernate.orm.runtime.recording.RecordedConfig;

public class QuarkusRuntimeInitDialectFactoryInitiator implements StandardServiceInitiator<DialectFactory> {
Expand All @@ -18,17 +19,26 @@ public class QuarkusRuntimeInitDialectFactoryInitiator implements StandardServic
private final Dialect dialect;
private final Optional<String> datasourceName;
private final DatabaseVersion buildTimeDbVersion;
private final boolean versionCheckEnabled;

public QuarkusRuntimeInitDialectFactoryInitiator(String persistenceUnitName,
boolean isFromPersistenceXml, Dialect dialect,
RecordedConfig recordedConfig) {
RecordedConfig recordedConfig,
HibernateOrmRuntimeConfigPersistenceUnit runtimePuConfig) {
this.persistenceUnitName = persistenceUnitName;
this.isFromPersistenceXml = isFromPersistenceXml;
this.dialect = dialect;
this.datasourceName = recordedConfig.getDataSource();
// We set the version from the dialect since if it wasn't provided explicitly through the `recordedConfig.getDbVersion()`
// then the version from `DialectVersions.Defaults` will be used:
this.buildTimeDbVersion = dialect.getVersion();
this.versionCheckEnabled = runtimePuConfig.database().versionCheckEnabled()
// TODO change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
// TODO disable the check by default when offline startup is opted in
// See https://github.com/quarkusio/quarkus/issues/13522
.orElse(recordedConfig.getExplicitDialect().isEmpty());
}

@Override
Expand All @@ -39,6 +49,6 @@ public Class<DialectFactory> getServiceInitiated() {
@Override
public DialectFactory initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
return new QuarkusRuntimeInitDialectFactory(persistenceUnitName, isFromPersistenceXml, dialect, datasourceName,
buildTimeDbVersion);
buildTimeDbVersion, versionCheckEnabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ public void buildReactivePersistenceUnit(
persistenceUnitDescriptors.produce(new PersistenceUnitDescriptorBuildItem(reactivePU,
new RecordedConfig(Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
dbKindOptional, Optional.empty(),
persistenceUnitConfig.dialect().dialect(),
io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy.NONE,
hibernateOrmConfig.database().ormCompatibilityVersion(),
persistenceUnitConfig.unsupportedProperties()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
RuntimeSettings runtimeSettings = runtimeSettingsBuilder.build();

StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(
runtimeSettings, recordedState, persistenceUnitName);
persistenceUnitName, recordedState, runtimeSettings, puConfig);

final Object cdiBeanManager = Arc.container().beanManager();
final Object validatorFactory = Arc.container().instance("quarkus-hibernate-validator-factory").get();
Expand All @@ -209,11 +209,10 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
return null;
}

private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(RuntimeSettings runtimeSettings,
RecordedState rs,
String persistenceUnitName) {
private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(String persistenceUnitName, RecordedState rs,
RuntimeSettings runtimeSettings, HibernateOrmRuntimeConfigPersistenceUnit puConfig) {
PreconfiguredReactiveServiceRegistryBuilder serviceRegistryBuilder = new PreconfiguredReactiveServiceRegistryBuilder(
persistenceUnitName, rs);
persistenceUnitName, rs, puConfig);

registerVertxAndPool(persistenceUnitName, runtimeSettings, serviceRegistryBuilder);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator;
import org.hibernate.tool.schema.internal.SchemaManagementToolInitiator;

import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfigPersistenceUnit;
import io.quarkus.hibernate.orm.runtime.boot.registry.MirroringIntegratorService;
import io.quarkus.hibernate.orm.runtime.cdi.QuarkusManagedBeanRegistryInitiator;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusJndiServiceInitiator;
Expand Down Expand Up @@ -71,9 +72,10 @@ public class PreconfiguredReactiveServiceRegistryBuilder {
private final Collection<Integrator> integrators;
private final StandardServiceRegistryImpl destroyedRegistry;

public PreconfiguredReactiveServiceRegistryBuilder(String puName, RecordedState rs) {
public PreconfiguredReactiveServiceRegistryBuilder(String puName, RecordedState rs,
HibernateOrmRuntimeConfigPersistenceUnit puConfig) {
checkIsReactive(rs);
this.initiators = buildQuarkusServiceInitiatorList(puName, rs);
this.initiators = buildQuarkusServiceInitiatorList(puName, rs, puConfig);
this.integrators = rs.getIntegrators();
this.destroyedRegistry = (StandardServiceRegistryImpl) rs.getMetadata()
.getMetadataBuildingOptions()
Expand Down Expand Up @@ -141,7 +143,8 @@ private BootstrapServiceRegistry buildEmptyBootstrapServiceRegistry() {
*
* @return
*/
private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorList(String puName, RecordedState rs) {
private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorList(String puName, RecordedState rs,
HibernateOrmRuntimeConfigPersistenceUnit puConfig) {
final ArrayList<StandardServiceInitiator<?>> serviceInitiators = new ArrayList<>();

//References to this object need to be injected in both the initiator for BytecodeProvider and for
Expand Down Expand Up @@ -203,7 +206,7 @@ private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorLis

// Custom one: Dialect is injected explicitly
serviceInitiators.add(new QuarkusRuntimeInitDialectFactoryInitiator(puName, rs.isFromPersistenceXml(),
rs.getDialect(), rs.getBuildTimeSettings().getSource()));
rs.getDialect(), rs.getBuildTimeSettings().getSource(), puConfig));

// Default implementation
serviceInitiators.add(BatchBuilderInitiator.INSTANCE);
Expand Down

0 comments on commit abdf9fa

Please sign in to comment.