From 6daa2ea1952c0326afd232f453d378d01296e268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 8 Oct 2024 10:12:39 +0200 Subject: [PATCH] Add quarkus.hibernate-orm.database.version-check.enabled 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 (`quarkus.hibernate-orm.dialect=something`), 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). --- docs/src/main/asciidoc/hibernate-orm.adoc | 15 +++- .../orm/deployment/HibernateOrmProcessor.java | 2 + ...sabledAutomaticallyPersistenceXmlTest.java | 73 +++++++++++++++++++ ...VersionCheckDisabledAutomaticallyTest.java | 69 ++++++++++++++++++ .../DbVersionCheckDisabledExplicitlyTest.java | 68 +++++++++++++++++ .../config/dialect/DbVersionInvalidTest.java | 3 +- ...rsion-placeholder-and-explicit-dialect.xml | 30 ++++++++ .../FastBootHibernatePersistenceProvider.java | 10 +-- ...ernateOrmRuntimeConfigPersistenceUnit.java | 18 +++++ .../PreconfiguredServiceRegistryBuilder.java | 21 +++--- .../orm/runtime/recording/RecordedConfig.java | 9 ++- .../QuarkusRuntimeInitDialectFactory.java | 20 ++++- ...kusRuntimeInitDialectFactoryInitiator.java | 14 +++- .../HibernateReactiveProcessor.java | 1 + ...tHibernateReactivePersistenceProvider.java | 9 +-- ...figuredReactiveServiceRegistryBuilder.java | 11 ++- 16 files changed, 341 insertions(+), 32 deletions(-) create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyPersistenceXmlTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledExplicitlyTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/resources/META-INF/some-persistence-with-h2-version-placeholder-and-explicit-dialect.xml diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index 47a7abe935069..e8d9c7c3f5c00 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -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 <>. + +// 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 check 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]] diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 2aa61f22adc2f..d78f8dc8a8691 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -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 @@ -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()), diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyPersistenceXmlTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyPersistenceXmlTest.java new file mode 100644 index 0000000000000..28084a93e099d --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyPersistenceXmlTest.java @@ -0,0 +1,73 @@ +package io.quarkus.hibernate.orm.config.dialect; + +import static io.quarkus.hibernate.orm.ResourceUtil.loadResourceAndReplacePlaceholders; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.MyEntity; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.hibernate.orm.runtime.config.DialectVersions; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that the workaround for https://github.com/quarkusio/quarkus/issues/43703 / + * https://github.com/quarkusio/quarkus/issues/42255 + * is effective. + */ +// TODO remove this test when 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 +public class DbVersionCheckDisabledAutomaticallyPersistenceXmlTest { + + private static final String ACTUAL_H2_VERSION = DialectVersions.Defaults.H2; + // We will set the DB version to something higher than the actual version: this is invalid. + private static final String CONFIGURED_DB_VERSION = "999.999.0"; + static { + assertThat(ACTUAL_H2_VERSION) + .as("Test setup - we need the required version to be different from the actual one") + .doesNotStartWith(CONFIGURED_DB_VERSION); + } + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(SmokeTestUtils.class) + .addClass(MyEntity.class) + .addAsManifestResource(new StringAsset(loadResourceAndReplacePlaceholders( + "META-INF/some-persistence-with-h2-version-placeholder-and-explicit-dialect.xml", + Map.of("H2_VERSION", "999.999"))), + "persistence.xml")) + .withConfigurationResource("application-datasource-only.properties"); + + @Inject + SessionFactory sessionFactory; + + @Inject + Session session; + + @Test + public void dialectVersion() { + var dialectVersion = sessionFactory.unwrap(SessionFactoryImplementor.class).getJdbcServices().getDialect().getVersion(); + assertThat(DialectVersions.toString(dialectVersion)).isEqualTo(CONFIGURED_DB_VERSION); + } + + @Test + @Transactional + public void smokeTest() { + SmokeTestUtils.testSimplePersistRetrieveUpdateDelete(session, + MyEntity.class, MyEntity::new, + MyEntity::getId, + MyEntity::setName, MyEntity::getName); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyTest.java new file mode 100644 index 0000000000000..9b526700c120c --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledAutomaticallyTest.java @@ -0,0 +1,69 @@ +package io.quarkus.hibernate.orm.config.dialect; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.MyEntity; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.hibernate.orm.runtime.config.DialectVersions; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that the workaround for https://github.com/quarkusio/quarkus/issues/43703 / + * https://github.com/quarkusio/quarkus/issues/42255 + * is effective. + */ +// TODO remove this test when 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 +public class DbVersionCheckDisabledAutomaticallyTest { + + private static final String ACTUAL_H2_VERSION = DialectVersions.Defaults.H2; + // We will set the DB version to something higher than the actual version: this is invalid. + private static final String CONFIGURED_DB_VERSION = "999.999.0"; + static { + assertThat(ACTUAL_H2_VERSION) + .as("Test setup - we need the required version to be different from the actual one") + .doesNotStartWith(CONFIGURED_DB_VERSION); + } + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(SmokeTestUtils.class) + .addClass(MyEntity.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.datasource.db-version", "999.999") + // Setting a dialect should disable the version check, so Quarkus should boot just fine + .overrideConfigKey("quarkus.hibernate-orm.dialect", H2Dialect.class.getName()); + + @Inject + SessionFactory sessionFactory; + + @Inject + Session session; + + @Test + public void dialectVersion() { + var dialectVersion = sessionFactory.unwrap(SessionFactoryImplementor.class).getJdbcServices().getDialect().getVersion(); + assertThat(DialectVersions.toString(dialectVersion)).isEqualTo(CONFIGURED_DB_VERSION); + } + + @Test + @Transactional + public void smokeTest() { + SmokeTestUtils.testSimplePersistRetrieveUpdateDelete(session, + MyEntity.class, MyEntity::new, + MyEntity::getId, + MyEntity::setName, MyEntity::getName); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledExplicitlyTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledExplicitlyTest.java new file mode 100644 index 0000000000000..9a15f506ed393 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionCheckDisabledExplicitlyTest.java @@ -0,0 +1,68 @@ +package io.quarkus.hibernate.orm.config.dialect; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.MyEntity; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.hibernate.orm.runtime.config.DialectVersions; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that the workaround for https://github.com/quarkusio/quarkus/issues/43703 / + * https://github.com/quarkusio/quarkus/issues/42255 + * is effective. + */ +// TODO remove this test when 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 +public class DbVersionCheckDisabledExplicitlyTest { + + private static final String ACTUAL_H2_VERSION = DialectVersions.Defaults.H2; + // We will set the DB version to something higher than the actual version: this is invalid. + private static final String CONFIGURED_DB_VERSION = "999.999.0"; + static { + assertThat(ACTUAL_H2_VERSION) + .as("Test setup - we need the required version to be different from the actual one") + .doesNotStartWith(CONFIGURED_DB_VERSION); + } + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(SmokeTestUtils.class) + .addClass(MyEntity.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.datasource.db-version", "999.999") + // We disable the version check explicitly, so Quarkus should boot just fine + .overrideConfigKey("quarkus.hibernate-orm.database.version-check.enabled", "false"); + + @Inject + SessionFactory sessionFactory; + + @Inject + Session session; + + @Test + public void dialectVersion() { + var dialectVersion = sessionFactory.unwrap(SessionFactoryImplementor.class).getJdbcServices().getDialect().getVersion(); + assertThat(DialectVersions.toString(dialectVersion)).isEqualTo(CONFIGURED_DB_VERSION); + } + + @Test + @Transactional + public void smokeTest() { + SmokeTestUtils.testSimplePersistRetrieveUpdateDelete(session, + MyEntity.class, MyEntity::new, + MyEntity::getId, + MyEntity::setName, MyEntity::getName); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionInvalidTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionInvalidTest.java index 42ca5d43b0f65..5bf55550014bf 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionInvalidTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/dialect/DbVersionInvalidTest.java @@ -49,7 +49,8 @@ public class DbVersionInvalidTest { "Consider upgrading your database", "Alternatively, rebuild your application with 'quarkus.datasource.db-version=" + ACTUAL_H2_VERSION_REPORTED + "'", - "this may disable some features and/or impact performance negatively")); + "this may disable some features and/or impact performance negatively", + "disable the check with 'quarkus.hibernate-orm.database.version-check.enabled=false'")); @Inject SessionFactory sessionFactory; diff --git a/extensions/hibernate-orm/deployment/src/test/resources/META-INF/some-persistence-with-h2-version-placeholder-and-explicit-dialect.xml b/extensions/hibernate-orm/deployment/src/test/resources/META-INF/some-persistence-with-h2-version-placeholder-and-explicit-dialect.xml new file mode 100644 index 0000000000000..2b5cd2b54d3c7 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/resources/META-INF/some-persistence-with-h2-version-placeholder-and-explicit-dialect.xml @@ -0,0 +1,30 @@ + + + + + Hibernate test case template Persistence Unit + + io.quarkus.hibernate.orm.MyEntity + + + + + + + + + + + + + + + + diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index 6c37f8b83a99a..4e9b779d84a62 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -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(); @@ -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); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java index 61fe9ba2c4b6b..09cafed1b27fb 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java @@ -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 @@ -101,6 +102,23 @@ interface HibernateOrmConfigPersistenceUnitDatabase { @WithConverter(TrimmedStringConverter.class) Optional 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 versionCheckEnabled(); } @ConfigGroup diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java index 0a99937ff7af2..9f517c2d2b38f 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java @@ -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; @@ -65,20 +66,21 @@ public class PreconfiguredServiceRegistryBuilder { private final Collection 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; @@ -148,7 +150,8 @@ private BootstrapServiceRegistry buildEmptyBootstrapServiceRegistry() { * * @return */ - private static List> buildQuarkusServiceInitiatorList(String puName, RecordedState rs) { + private static List> buildQuarkusServiceInitiatorList(String puName, RecordedState rs, + HibernateOrmRuntimeConfigPersistenceUnit puConfig) { final ArrayList> serviceInitiators = new ArrayList>(); //References to this object need to be injected in both the initiator for BytecodeProvider and for @@ -202,7 +205,7 @@ private static List> 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); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedConfig.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedConfig.java index 368826a47ced7..2556e3bf16198 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedConfig.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedConfig.java @@ -15,13 +15,15 @@ public class RecordedConfig { private final Optional dataSource; private final Optional dbKind; private final Optional dbVersion; + private final Optional explicitDialect; private final MultiTenancyStrategy multiTenancyStrategy; private final Map quarkusConfigUnsupportedProperties; private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion; @RecordableConstructor public RecordedConfig(Optional dataSource, Optional dbKind, - Optional dbVersion, MultiTenancyStrategy multiTenancyStrategy, + Optional dbVersion, Optional explicitDialect, + MultiTenancyStrategy multiTenancyStrategy, DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion, Map quarkusConfigUnsupportedProperties) { Objects.requireNonNull(dataSource); @@ -31,6 +33,7 @@ public RecordedConfig(Optional dataSource, Optional dbKind, this.dataSource = dataSource; this.dbKind = dbKind; this.dbVersion = dbVersion; + this.explicitDialect = explicitDialect; this.multiTenancyStrategy = multiTenancyStrategy; this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties; this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion; @@ -48,6 +51,10 @@ public Optional getDbVersion() { return dbVersion; } + public Optional getExplicitDialect() { + return explicitDialect; + } + public MultiTenancyStrategy getMultiTenancyStrategy() { return multiTenancyStrategy; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactory.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactory.java index d866e161c25f5..5924d80eac29b 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactory.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactory.java @@ -16,6 +16,7 @@ import org.hibernate.internal.EntityManagerMessageLogger; import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; import io.quarkus.hibernate.orm.runtime.config.DialectVersions; import io.quarkus.runtime.configuration.ConfigurationException; @@ -32,29 +33,36 @@ public class QuarkusRuntimeInitDialectFactory implements DialectFactory { private final Dialect dialect; private final Optional datasourceName; private final DatabaseVersion buildTimeDbVersion; + private final boolean versionCheckEnabled; private boolean triedToRetrieveDbVersion = false; private Optional actualDbVersion = Optional.empty(); public QuarkusRuntimeInitDialectFactory(String persistenceUnitName, boolean isFromPersistenceXml, Dialect dialect, - Optional datasourceName, DatabaseVersion buildTimeDbVersion) { + Optional 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 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)); @@ -78,6 +86,14 @@ public void checkActualDbVersion() { : DataSourceUtil.dataSourcePropertyKey(datasourceName.get(), "db-version"), DialectVersions.toString(actualDbVersion.get()))); } + if (!isFromPersistenceXml) { + errorMessage.append(String.format(Locale.ROOT, + " As a last resort," + + " if you are certain your application will work correctly even though the database version is incorrect," + + " disable the check with" + + " '%1$s=false'.", + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "database.version-check.enabled"))); + } throw new ConfigurationException(errorMessage.toString()); } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactoryInitiator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactoryInitiator.java index e179be5b5678c..03730f2f7c1af 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactoryInitiator.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusRuntimeInitDialectFactoryInitiator.java @@ -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 { @@ -18,10 +19,12 @@ public class QuarkusRuntimeInitDialectFactoryInitiator implements StandardServic private final Dialect dialect; private final Optional 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; @@ -29,6 +32,13 @@ public QuarkusRuntimeInitDialectFactoryInitiator(String persistenceUnitName, // 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 @@ -39,6 +49,6 @@ public Class getServiceInitiated() { @Override public DialectFactory initiateService(Map configurationValues, ServiceRegistryImplementor registry) { return new QuarkusRuntimeInitDialectFactory(persistenceUnitName, isFromPersistenceXml, dialect, datasourceName, - buildTimeDbVersion); + buildTimeDbVersion, versionCheckEnabled); } } diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index aca38d794029e..f006d1835105e 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -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()), diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java index 51294feb0bd6d..aaf39c0596a95 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java @@ -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(); @@ -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); diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java index b4ef47ac467c7..dd231b1af9c29 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java @@ -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; @@ -71,9 +72,10 @@ public class PreconfiguredReactiveServiceRegistryBuilder { private final Collection 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() @@ -141,7 +143,8 @@ private BootstrapServiceRegistry buildEmptyBootstrapServiceRegistry() { * * @return */ - private static List> buildQuarkusServiceInitiatorList(String puName, RecordedState rs) { + private static List> buildQuarkusServiceInitiatorList(String puName, RecordedState rs, + HibernateOrmRuntimeConfigPersistenceUnit puConfig) { final ArrayList> serviceInitiators = new ArrayList<>(); //References to this object need to be injected in both the initiator for BytecodeProvider and for @@ -203,7 +206,7 @@ private static List> 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);