From 0def601dbada0318cdb0afa45688e5b60be10884 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 4 Jun 2025 09:02:03 +0200 Subject: [PATCH 1/3] [#31974] Add HibernateProcessorUtil class The goal is to have the common configuration for Hibernate ORM and Hibernate Reactive so that initialization is consistent between the two --- .../util/HibernateProcessorUtil.java | 361 ++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/util/HibernateProcessorUtil.java diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/util/HibernateProcessorUtil.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/util/HibernateProcessorUtil.java new file mode 100644 index 0000000000000..8596fc0c4e845 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/util/HibernateProcessorUtil.java @@ -0,0 +1,361 @@ +package io.quarkus.hibernate.orm.deployment.util; + +import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Properties; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import jakarta.persistence.SharedCacheMode; +import jakarta.persistence.ValidationMode; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.id.SequenceMismatchStrategy; +import org.hibernate.jpa.boot.spi.JpaSettings; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.loader.BatchFetchStyle; +import org.jboss.logging.Logger; + +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; +import io.quarkus.deployment.builditem.SystemPropertyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.hibernate.orm.deployment.HibernateConfigUtil; +import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig; +import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit; +import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem; +import io.quarkus.hibernate.orm.deployment.spi.DatabaseKindDialectBuildItem; +import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; +import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDescriptor; +import io.quarkus.hibernate.orm.runtime.customized.FormatMapperKind; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.configuration.ConfigurationException; + +/** + * A set of utilities method to collect the common operations needed to configure the + * Hibernate ORM and Hibernate Reactive extensions. + */ +public final class HibernateProcessorUtil { + private static final Logger LOG = Logger.getLogger(HibernateProcessorUtil.class); + private static final int DEFAULT_BATCH_SIZE = 16; + public static final String NO_SQL_LOAD_SCRIPT_FILE = "no-file"; + + private HibernateProcessorUtil() { + } + + public static boolean hasEntities(JpaModelBuildItem jpaModel) { + return !jpaModel.getEntityClassNames().isEmpty(); + } + + public static Optional jsonMapperKind(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JACKSON)) { + return Optional.of(FormatMapperKind.JACKSON); + } else if (capabilities.isPresent(Capability.JSONB)) { + return Optional.of(FormatMapperKind.JSONB); + } else { + return Optional.empty(); + } + } + + public static Optional xmlMapperKind(Capabilities capabilities) { + return capabilities.isPresent(Capability.JAXB) + ? Optional.of(FormatMapperKind.JAXB) + : Optional.empty(); + } + + public static boolean isHibernateValidatorPresent(Capabilities capabilities) { + return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR); + } + + public static void setDialectAndStorageEngine( + String persistenceUnitName, + Optional dbKind, + Optional explicitDialect, + Optional explicitDbMinVersion, + List dbKindDialectBuildItems, + Optional storageEngine, + BuildProducer systemProperties, + BiConsumer puPropertiesCollector, + Set storageEngineCollector) { + Optional dialect = explicitDialect; + Optional dbProductName = Optional.empty(); + Optional dbProductVersion = explicitDbMinVersion; + + if (dbKind.isPresent() || explicitDialect.isPresent()) { + for (DatabaseKindDialectBuildItem item : dbKindDialectBuildItems) { + if (dbKind.isPresent() && DatabaseKind.is(dbKind.get(), item.getDbKind()) + || explicitDialect.isPresent() && item.getMatchingDialects().contains(explicitDialect.get())) { + dbProductName = item.getDatabaseProductName(); + if (dbProductName.isEmpty() && explicitDialect.isEmpty()) { + dialect = item.getDialectOptional(); + } + if (explicitDbMinVersion.isEmpty()) { + dbProductVersion = item.getDefaultDatabaseProductVersion(); + } + break; + } + } + if (dialect.isEmpty() && dbProductName.isEmpty()) { + throw new ConfigurationException( + "Could not guess the dialect from the database kind '" + + dbKind.get() + + "'. Add an explicit '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + + "' property."); + } + } + + if (dialect.isPresent()) { + puPropertiesCollector.accept(AvailableSettings.DIALECT, dialect.get()); + } else if (dbProductName.isPresent()) { + puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_NAME, dbProductName.get()); + } + + if (storageEngine.isPresent()) { + if (isMySQLOrMariaDB(dbKind, dialect)) { + // The storage engine has to be set as a system property. + // We record it so that we can later run checks (because we can only set a single value) + storageEngineCollector.add(storageEngine.get()); + systemProperties.produce(new SystemPropertyBuildItem(AvailableSettings.STORAGE_ENGINE, storageEngine.get())); + } else { + LOG.warnf( + "The storage engine configuration is being ignored because the database is neither MySQL nor MariaDB."); + } + } + + if (dbProductVersion.isPresent()) { + puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get()); + } + } + + public static void configureProperties(QuarkusPersistenceUnitDescriptor desc, HibernateOrmConfigPersistenceUnit config, + HibernateOrmConfig hibernateOrmConfig) { + // Quoting strategy + configureQuoting(desc, config); + + // Physical Naming Strategy + config.physicalNamingStrategy().ifPresent(namingStrategy -> desc.getProperties() + .setProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, namingStrategy)); + + // Implicit Naming Strategy + config.implicitNamingStrategy().ifPresent(namingStrategy -> desc.getProperties() + .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy)); + + // Metadata builder contributor + config.metadataBuilderContributor().ifPresent(className -> desc.getProperties() + .setProperty(JpaSettings.METADATA_BUILDER_CONTRIBUTOR, className)); + + // Mapping + if (config.mapping().timezone().timeZoneDefaultStorage().isPresent()) { + desc.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, + config.mapping().timezone().timeZoneDefaultStorage().get().name()); + } + desc.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, + config.mapping().id().optimizer().idOptimizerDefault() + .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); + + //charset + desc.getProperties() + .setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, config.database().charset().name()); + + // Query + int batchSize = firstPresent(config.fetch().batchSize(), config.batchFetchSize()).orElse(DEFAULT_BATCH_SIZE); + if (batchSize > 0) { + desc.getProperties().setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, Integer.toString(batchSize)); + desc.getProperties().setProperty(AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.toString()); + } + + // Fetch + if (config.fetch().maxDepth().isPresent()) { + setMaxFetchDepth(desc, config.fetch().maxDepth()); + } else if (config.maxFetchDepth().isPresent()) { + setMaxFetchDepth(desc, config.maxFetchDepth()); + } + + desc.getProperties().setProperty(AvailableSettings.QUERY_PLAN_CACHE_MAX_SIZE, Integer.toString( + config.query().queryPlanCacheMaxSize())); + + desc.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, + config.query().defaultNullOrdering().name().toLowerCase(Locale.ROOT)); + + desc.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, + String.valueOf(config.query().inClauseParameterPadding())); + + // Disable sequence validations: they are reportedly slow, and people already get the same validation from normal schema validation + desc.getProperties().put(AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY, + SequenceMismatchStrategy.NONE); + + // JDBC + config.jdbc().timezone().ifPresent( + timezone -> desc.getProperties().setProperty(AvailableSettings.JDBC_TIME_ZONE, timezone)); + + config.jdbc().statementFetchSize().ifPresent( + fetchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_FETCH_SIZE, + String.valueOf(fetchSize))); + + config.jdbc().statementBatchSize().ifPresent( + fetchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, + String.valueOf(fetchSize))); + + // Statistics + if (hibernateOrmConfig.metrics().enabled() + || (hibernateOrmConfig.statistics().isPresent() && hibernateOrmConfig.statistics().get())) { + desc.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); + //When statistics are enabled, the default in Hibernate ORM is to also log them after each + // session; turn that off by default as it's very noisy: + desc.getProperties().setProperty(AvailableSettings.LOG_SESSION_METRICS, + String.valueOf(hibernateOrmConfig.logSessionMetrics().orElse(false))); + } + + // Caching + configureCaching(desc, config); + + // Validation + configureValidation(desc, config); + + // Discriminator Column + desc.getProperties().setProperty(AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS, + String.valueOf(config.discriminator().ignoreExplicitForJoined())); + } + + private static void setMaxFetchDepth(PersistenceUnitDescriptor descriptor, OptionalInt maxFetchDepth) { + descriptor.getProperties().setProperty(AvailableSettings.MAX_FETCH_DEPTH, String.valueOf(maxFetchDepth.getAsInt())); + } + + private static List getSqlLoadScript(Optional> sqlLoadScript, LaunchMode launchMode) { + if (sqlLoadScript.isPresent()) { + return sqlLoadScript.get().stream() + .filter(s -> !NO_SQL_LOAD_SCRIPT_FILE.equalsIgnoreCase(s)) + .collect(Collectors.toList()); + } + if (launchMode == LaunchMode.NORMAL) { + return Collections.emptyList(); + } + return List.of("import.sql"); + } + + private static boolean isMySQLOrMariaDB(Optional dbKind, Optional dialect) { + if (dbKind.isPresent() && (DatabaseKind.isMySQL(dbKind.get()) || DatabaseKind.isMariaDB(dbKind.get()))) { + return true; + } + if (dialect.isPresent()) { + String lowercaseDialect = dialect.get().toLowerCase(Locale.ROOT); + return lowercaseDialect.contains("mysql") || lowercaseDialect.contains("mariadb"); + } + return false; + } + + private static void configureCaching(QuarkusPersistenceUnitDescriptor descriptor, + HibernateOrmConfigPersistenceUnit config) { + if (config.secondLevelCachingEnabled()) { + Properties p = descriptor.getProperties(); + p.putIfAbsent(AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.TRUE); + p.putIfAbsent(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE); + p.putIfAbsent(AvailableSettings.USE_QUERY_CACHE, Boolean.TRUE); + p.putIfAbsent(AvailableSettings.JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE); + Map cacheConfigEntries = HibernateConfigUtil.getCacheConfigEntries(config); + for (Map.Entry entry : cacheConfigEntries.entrySet()) { + descriptor.getProperties().setProperty(entry.getKey(), entry.getValue()); + } + } else { + Properties p = descriptor.getProperties(); + p.put(AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.FALSE); + p.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE); + p.put(AvailableSettings.USE_QUERY_CACHE, Boolean.FALSE); + p.put(AvailableSettings.JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.NONE); + } + } + + private static void configureValidation(QuarkusPersistenceUnitDescriptor descriptor, + HibernateOrmConfigPersistenceUnit config) { + if (!config.validation().enabled()) { + descriptor.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name()); + } else { + descriptor.getProperties().setProperty( + AvailableSettings.JAKARTA_VALIDATION_MODE, + config.validation().mode() + .stream() + .map(Enum::name) + .collect(Collectors.joining(","))); + } + } + + private static void configureQuoting(QuarkusPersistenceUnitDescriptor desc, + HibernateOrmConfigPersistenceUnit persistenceUnitConfig) { + if (persistenceUnitConfig.quoteIdentifiers() + .strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ALL + || persistenceUnitConfig.quoteIdentifiers() + .strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS + || persistenceUnitConfig.database().globallyQuotedIdentifiers()) { + desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); + } + if (persistenceUnitConfig.quoteIdentifiers() + .strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { + desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); + } else if (persistenceUnitConfig.quoteIdentifiers() + .strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ONLY_KEYWORDS) { + desc.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); + } + } + + public static void configureSqlLoadScript(String persistenceUnitName, + HibernateOrmConfigPersistenceUnit persistenceUnitConfig, + ApplicationArchivesBuildItem applicationArchivesBuildItem, LaunchMode launchMode, + BuildProducer nativeImageResources, + BuildProducer hotDeploymentWatchedFiles, + QuarkusPersistenceUnitDescriptor descriptor) { + // sql-load-scripts + List importFiles = getSqlLoadScript(persistenceUnitConfig.sqlLoadScript(), launchMode); + if (!importFiles.isEmpty()) { + for (String importFile : importFiles) { + Path loadScriptPath; + try { + loadScriptPath = applicationArchivesBuildItem.getRootArchive().getChildPath(importFile); + } catch (RuntimeException e) { + throw new ConfigurationException( + "Unable to interpret path referenced in '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "=" + + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) + + "': " + e.getMessage()); + } + + if (loadScriptPath != null && !Files.isDirectory(loadScriptPath)) { + // enlist resource if present + nativeImageResources.produce(new NativeImageResourceBuildItem(importFile)); + } else if (persistenceUnitConfig.sqlLoadScript().isPresent()) { + //raise exception if explicit file is not present (i.e. not the default) + throw new ConfigurationException( + "Unable to find file referenced in '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "=" + + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) + + "'. Remove property or add file to your path."); + } + // in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist + // as a user could still add it after performing the initial configuration + hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile)); + } + + // only set the found import files if configured + if (persistenceUnitConfig.sqlLoadScript().isPresent()) { + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, String.join(",", importFiles)); + } + } else { + //Disable implicit loading of the default import script (import.sql) + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, ""); + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_SKIP_DEFAULT_IMPORT_FILE, "true"); + } + } +} From e6ffaf68ea4df6ed1ecf218f296e8dc6e6ee2b52 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 4 Jun 2025 09:06:10 +0200 Subject: [PATCH 2/3] [#31974][#28629] Refactor the Hibernate processors The configuration of Hibernate ORM and Hibernate Reactive processors will use the same method in HibernateProcessorUtil when possible. The original issue (#31974) was about the dialect initialization, but I moved more configuration properties because it made sense (#28629). --- .../deployment/HibernateOrmCdiProcessor.java | 5 +- .../orm/deployment/HibernateOrmProcessor.java | 331 ++---------------- .../HibernateReactiveProcessor.java | 317 ++--------------- 3 files changed, 56 insertions(+), 597 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index 4f4a9f02e3982..282c05f8a4e01 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.hibernate.orm.deployment; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.hasEntities; import static org.apache.commons.lang3.BooleanUtils.isFalse; import java.lang.reflect.Modifier; @@ -264,7 +265,7 @@ void registerBeans(HibernateOrmConfig hibernateOrmConfig, CombinedIndexBuildItem combinedIndex, List descriptors, JpaModelBuildItem jpaModel) { - if (!HibernateOrmProcessor.hasEntities(jpaModel)) { + if (!hasEntities(jpaModel)) { return; } @@ -293,7 +294,7 @@ void registerBeans(HibernateOrmConfig hibernateOrmConfig, void transformBeans(JpaModelBuildItem jpaModel, JpaModelIndexBuildItem indexBuildItem, BeanDiscoveryFinishedBuildItem beans, BuildProducer producer) { - if (!HibernateOrmProcessor.hasEntities(jpaModel)) { + if (!hasEntities(jpaModel)) { return; } 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 33513518cc1ba..ad5cb77d146e3 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 @@ -2,16 +2,16 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; -import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent; -import static org.hibernate.cfg.CacheSettings.JAKARTA_SHARED_CACHE_MODE; -import static org.hibernate.cfg.CacheSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES; -import static org.hibernate.cfg.CacheSettings.USE_QUERY_CACHE; -import static org.hibernate.cfg.CacheSettings.USE_SECOND_LEVEL_CACHE; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureProperties; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureSqlLoadScript; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.hasEntities; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.isHibernateValidatorPresent; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.jsonMapperKind; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.setDialectAndStorageEngine; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.xmlMapperKind; import java.io.IOException; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -36,8 +36,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; -import jakarta.persistence.SharedCacheMode; -import jakarta.persistence.ValidationMode; import jakarta.persistence.spi.PersistenceUnitTransactionType; import jakarta.xml.bind.JAXBElement; @@ -45,12 +43,8 @@ import org.hibernate.boot.archive.scan.spi.PackageDescriptor; import org.hibernate.boot.beanvalidation.BeanValidationIntegrator; import org.hibernate.cfg.AvailableSettings; -import org.hibernate.id.SequenceMismatchStrategy; import org.hibernate.integrator.spi.Integrator; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.jpa.boot.spi.JpaSettings; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; -import org.hibernate.loader.BatchFetchStyle; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.AnnotationValue; @@ -112,7 +106,6 @@ import io.quarkus.deployment.util.IoUtil; import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.hibernate.orm.PersistenceUnit; -import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationStaticConfiguredBuildItem; import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem; @@ -159,7 +152,6 @@ public final class HibernateOrmProcessor { public static final String HIBERNATE_ORM_CONFIG_PREFIX = "quarkus.hibernate-orm."; - public static final String NO_SQL_LOAD_SCRIPT_FILE = "no-file"; private static final Logger LOG = Logger.getLogger(HibernateOrmProcessor.class); @@ -802,23 +794,6 @@ public void registerInjectServiceMethodsForReflection(CombinedIndexBuildItem ind } } - private static List getSqlLoadScript(Optional> sqlLoadScript, LaunchMode launchMode) { - // Explicit file or default Hibernate ORM file. - if (sqlLoadScript.isPresent()) { - return sqlLoadScript.get().stream() - .filter(s -> !NO_SQL_LOAD_SCRIPT_FILE.equalsIgnoreCase(s)) - .collect(Collectors.toList()); - } else if (launchMode == LaunchMode.NORMAL) { - return Collections.emptyList(); - } else { - return List.of("import.sql"); - } - } - - static boolean hasEntities(JpaModelBuildItem jpaModel) { - return !jpaModel.getEntityClassNames().isEmpty(); - } - private void handleHibernateORMWithNoPersistenceXml( HibernateOrmConfig hibernateOrmConfig, CombinedIndexBuildItem index, @@ -960,179 +935,15 @@ private static void producePersistenceUnitDescriptorFromConfig( false); MultiTenancyStrategy multiTenancyStrategy = getMultiTenancyStrategy(persistenceUnitConfig.multitenant()); + collectDialectConfig(persistenceUnitName, persistenceUnitConfig, dbKindMetadataBuildItems, jdbcDataSource, multiTenancyStrategy, systemProperties, reflectiveMethods, descriptor.getProperties()::setProperty, storageEngineCollector); - // Physical Naming Strategy - persistenceUnitConfig.physicalNamingStrategy().ifPresent( - namingStrategy -> descriptor.getProperties() - .setProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, namingStrategy)); - - // Implicit Naming Strategy - persistenceUnitConfig.implicitNamingStrategy().ifPresent( - namingStrategy -> descriptor.getProperties() - .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy)); - - // Metadata builder contributor - persistenceUnitConfig.metadataBuilderContributor().ifPresent( - className -> descriptor.getProperties() - .setProperty(JpaSettings.METADATA_BUILDER_CONTRIBUTOR, className)); - - // Mapping - if (persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().isPresent()) { - descriptor.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, - persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().get().name()); - } - descriptor.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, - persistenceUnitConfig.mapping().id().optimizer().idOptimizerDefault() - .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); - - //charset - descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, - persistenceUnitConfig.database().charset().name()); - - // Quoting strategy - if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL - || persistenceUnitConfig.quoteIdentifiers() - .strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS - || persistenceUnitConfig.database().globallyQuotedIdentifiers()) { - descriptor.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); - } - if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { - descriptor.getProperties().setProperty( - AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); - } else if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ONLY_KEYWORDS) { - descriptor.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); - } - - // Query - int batchSize = firstPresent(persistenceUnitConfig.fetch().batchSize(), persistenceUnitConfig.batchFetchSize()) - .orElse(16); - if (batchSize > 0) { - descriptor.getProperties().setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, - Integer.toString(batchSize)); - descriptor.getProperties().setProperty(AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.toString()); - } - - if (persistenceUnitConfig.fetch().maxDepth().isPresent()) { - setMaxFetchDepth(descriptor, persistenceUnitConfig.fetch().maxDepth()); - } else if (persistenceUnitConfig.maxFetchDepth().isPresent()) { - setMaxFetchDepth(descriptor, persistenceUnitConfig.maxFetchDepth()); - } - - descriptor.getProperties().setProperty(AvailableSettings.QUERY_PLAN_CACHE_MAX_SIZE, Integer.toString( - persistenceUnitConfig.query().queryPlanCacheMaxSize())); - - descriptor.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, - persistenceUnitConfig.query().defaultNullOrdering().name().toLowerCase(Locale.ROOT)); - - descriptor.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, - String.valueOf(persistenceUnitConfig.query().inClauseParameterPadding())); - - // Disable sequence validations: they are reportedly slow, and people already get the same validation from normal schema validation - descriptor.getProperties().put(AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY, - SequenceMismatchStrategy.NONE); - - // JDBC - persistenceUnitConfig.jdbc().timezone().ifPresent( - timezone -> descriptor.getProperties().setProperty(AvailableSettings.JDBC_TIME_ZONE, timezone)); - - persistenceUnitConfig.jdbc().statementFetchSize().ifPresent( - fetchSize -> descriptor.getProperties().setProperty(AvailableSettings.STATEMENT_FETCH_SIZE, - String.valueOf(fetchSize))); - - persistenceUnitConfig.jdbc().statementBatchSize().ifPresent( - fetchSize -> descriptor.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, - String.valueOf(fetchSize))); - - // Statistics - if (hibernateOrmConfig.metrics().enabled() - || (hibernateOrmConfig.statistics().isPresent() && hibernateOrmConfig.statistics().get())) { - descriptor.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); - //When statistics are enabled, the default in Hibernate ORM is to also log them after each - // session; turn that off by default as it's very noisy: - descriptor.getProperties().setProperty(AvailableSettings.LOG_SESSION_METRICS, - String.valueOf(hibernateOrmConfig.logSessionMetrics().orElse(false))); - } - - // sql-load-scripts - List importFiles = getSqlLoadScript(persistenceUnitConfig.sqlLoadScript(), launchMode); - - if (!importFiles.isEmpty()) { - for (String importFile : importFiles) { - Path loadScriptPath; - try { - loadScriptPath = applicationArchivesBuildItem.getRootArchive().getChildPath(importFile); - } catch (RuntimeException e) { - throw new ConfigurationException( - "Unable to interpret path referenced in '" - + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "=" - + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) - + "': " + e.getMessage()); - } + configureProperties(descriptor, persistenceUnitConfig, hibernateOrmConfig); - if (loadScriptPath != null && !Files.isDirectory(loadScriptPath)) { - // enlist resource if present - nativeImageResources.produce(new NativeImageResourceBuildItem(importFile)); - } else if (persistenceUnitConfig.sqlLoadScript().isPresent()) { - //raise exception if explicit file is not present (i.e. not the default) - throw new ConfigurationException( - "Unable to find file referenced in '" - + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "=" - + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) - + "'. Remove property or add file to your path."); - } - // in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist - // as a user could still add it after performing the initial configuration - hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile)); - } - - // only set the found import files if configured - if (persistenceUnitConfig.sqlLoadScript().isPresent()) { - descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, String.join(",", importFiles)); - } - } else { - //Disable implicit loading of the default import script (import.sql) - descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, ""); - descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_SKIP_DEFAULT_IMPORT_FILE, "true"); - } - - // Caching - if (persistenceUnitConfig.secondLevelCachingEnabled()) { - Properties p = descriptor.getProperties(); - //Only set these if the user isn't making an explicit choice: - p.putIfAbsent(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.TRUE); - p.putIfAbsent(USE_SECOND_LEVEL_CACHE, Boolean.TRUE); - p.putIfAbsent(USE_QUERY_CACHE, Boolean.TRUE); - p.putIfAbsent(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE); - Map cacheConfigEntries = HibernateConfigUtil.getCacheConfigEntries(persistenceUnitConfig); - for (Entry entry : cacheConfigEntries.entrySet()) { - descriptor.getProperties().setProperty(entry.getKey(), entry.getValue()); - } - } else { - //Unless the global switch is explicitly set to off, in which case we disable all caching: - Properties p = descriptor.getProperties(); - p.put(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.FALSE); - p.put(USE_SECOND_LEVEL_CACHE, Boolean.FALSE); - p.put(USE_QUERY_CACHE, Boolean.FALSE); - p.put(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.NONE); - } - - if (!persistenceUnitConfig.validation().enabled()) { - descriptor.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name()); - } else { - descriptor.getProperties().setProperty( - AvailableSettings.JAKARTA_VALIDATION_MODE, - persistenceUnitConfig.validation().mode() - .stream() - .map(Enum::name) - .collect(Collectors.joining(","))); - } - - // Discriminator Column - descriptor.getProperties().setProperty(AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS, - String.valueOf(persistenceUnitConfig.discriminator().ignoreExplicitForJoined())); + configureSqlLoadScript(persistenceUnitName, persistenceUnitConfig, applicationArchivesBuildItem, launchMode, + nativeImageResources, hotDeploymentWatchedFiles, descriptor); Optional jsonMapper = jsonMapperKind(capabilities); Optional xmlMapper = xmlMapperKind(capabilities); @@ -1158,12 +969,14 @@ private static void producePersistenceUnitDescriptorFromConfig( private static void collectDialectConfig(String persistenceUnitName, HibernateOrmConfigPersistenceUnit persistenceUnitConfig, - List dbKindMetadataBuildItems, Optional jdbcDataSource, + List dbKindMetadataBuildItems, + Optional jdbcDataSource, MultiTenancyStrategy multiTenancyStrategy, BuildProducer systemProperties, BuildProducer reflectiveMethods, - BiConsumer puPropertiesCollector, Set storageEngineCollector) { - Optional explicitDialect = persistenceUnitConfig.dialect().dialect(); + BiConsumer puPropertiesCollector, + Set storageEngineCollector) { + Optional dialect = persistenceUnitConfig.dialect().dialect(); Optional dbKind = jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind); Optional explicitDbMinVersion = jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion); if (multiTenancyStrategy != MultiTenancyStrategy.DATABASE && jdbcDataSource.isEmpty()) { @@ -1175,69 +988,16 @@ private static void collectDialectConfig(String persistenceUnitName, "quarkus.datasource.password", "quarkus.datasource.jdbc.url"))); } - Optional dialect = explicitDialect; - Optional dbProductName = Optional.empty(); - Optional dbProductVersion = explicitDbMinVersion; - if (dbKind.isPresent() || explicitDialect.isPresent()) { - for (DatabaseKindDialectBuildItem item : dbKindMetadataBuildItems) { - if (dbKind.isPresent() && DatabaseKind.is(dbKind.get(), item.getDbKind()) - // Set the default version based on the dialect when we don't have a datasource - // (i.e. for database multi-tenancy) - || explicitDialect.isPresent() && item.getMatchingDialects().contains(explicitDialect.get())) { - dbProductName = item.getDatabaseProductName(); - if (dbProductName.isEmpty() && explicitDialect.isEmpty()) { - // Use dialects only as a last resort, prefer product name or explicitly user-provided dialect - dialect = item.getDialectOptional(); - } - if (explicitDbMinVersion.isEmpty()) { - dbProductVersion = item.getDefaultDatabaseProductVersion(); - } - break; - } - } - if (dialect.isEmpty() && dbProductName.isEmpty()) { - throw new ConfigurationException( - "The Hibernate ORM extension could not guess the dialect from the database kind '" + dbKind.get() - + "'. Add an explicit '" - + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") - + "' property."); - } - } - - if (dialect.isPresent()) { - puPropertiesCollector.accept(AvailableSettings.DIALECT, dialect.get()); - } else if (dbProductName.isPresent()) { - puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_NAME, dbProductName.get()); - } else { - // We only get here with the database multi-tenancy strategy; see the initial check, up top. - assert multiTenancyStrategy == MultiTenancyStrategy.DATABASE; - throw new ConfigurationException(String.format(Locale.ROOT, - "The Hibernate ORM extension could not infer the dialect for persistence unit '%s'." - + " When using database multi-tenancy, you must either configure a datasource for that persistence unit" - + " (refer to https://quarkus.io/guides/datasource for guidance)," - + " or set the dialect explicitly through property '" - + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + "'.", - persistenceUnitName)); - } - - if (persistenceUnitConfig.dialect().storageEngine().isPresent()) { - // Only actually set the storage engines if MySQL or MariaDB - if (isMySQLOrMariaDB(dbKind, dialect)) { - // The storage engine has to be set as a system property. - // We record it so that we can later run checks (because we can only set a single value) - storageEngineCollector.add(persistenceUnitConfig.dialect().storageEngine().get()); - systemProperties.produce(new SystemPropertyBuildItem(AvailableSettings.STORAGE_ENGINE, - persistenceUnitConfig.dialect().storageEngine().get())); - } else { - LOG.warnf("The storage engine set through configuration property '%1$s' is being ignored" - + " because the database is neither MySQL nor MariaDB.", - HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect.storage-engine")); - } - } - - if (dbProductVersion.isPresent()) { - puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get()); - } + setDialectAndStorageEngine( + persistenceUnitName, + dbKind, + dialect, + explicitDbMinVersion, + dbKindMetadataBuildItems, + persistenceUnitConfig.dialect().storageEngine(), + systemProperties, + puPropertiesCollector, + storageEngineCollector); if ((dbKind.isPresent() && DatabaseKind.isPostgreSQL(dbKind.get()) || (dialect.isPresent() && dialect.get().toLowerCase(Locale.ROOT).contains("postgres")))) { @@ -1684,45 +1444,6 @@ private boolean isModified(String entity, Set changedClasses, IndexView return false; } - private static Class[] toArray(final Set> interfaces) { - if (interfaces == null) { - return ArrayHelper.EMPTY_CLASS_ARRAY; - } - return interfaces.toArray(new Class[interfaces.size()]); - } - - private static boolean isMySQLOrMariaDB(Optional dbKind, Optional dialect) { - if (dbKind.isPresent() && (DatabaseKind.isMySQL(dbKind.get()) || DatabaseKind.isMariaDB(dbKind.get()))) { - return true; - } - if (dialect.isPresent()) { - String lowercaseDialect = dialect.get().toLowerCase(Locale.ROOT); - return lowercaseDialect.contains("mysql") || lowercaseDialect.contains("mariadb"); - } - return false; - } - - private static Optional jsonMapperKind(Capabilities capabilities) { - if (capabilities.isPresent(Capability.JACKSON)) { - return Optional.of(FormatMapperKind.JACKSON); - } - if (capabilities.isPresent(Capability.JSONB)) { - return Optional.of(FormatMapperKind.JSONB); - } - return Optional.empty(); - } - - private static Optional xmlMapperKind(Capabilities capabilities) { - if (capabilities.isPresent(Capability.JAXB)) { - return Optional.of(FormatMapperKind.JAXB); - } - return Optional.empty(); - } - - private static boolean isHibernateValidatorPresent(Capabilities capabilities) { - return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR); - } - private static final class ProxyCache { Map cache = new HashMap<>(); 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 479f1edbf3356..b9e83a00a928d 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 @@ -2,48 +2,38 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; -import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureProperties; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureSqlLoadScript; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.hasEntities; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.isHibernateValidatorPresent; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.jsonMapperKind; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.setDialectAndStorageEngine; +import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.xmlMapperKind; import static io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME; -import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION; -import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_MODE; -import static org.hibernate.cfg.AvailableSettings.STORAGE_ENGINE; -import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES; -import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE; -import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE; - -import java.nio.file.Files; -import java.nio.file.Path; + import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; -import java.util.OptionalInt; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import jakarta.persistence.SharedCacheMode; -import jakarta.persistence.ValidationMode; import jakarta.persistence.spi.PersistenceUnitTransactionType; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.loader.BatchFetchStyle; import org.hibernate.reactive.provider.impl.ReactiveIntegrator; import org.jboss.logging.Logger; import io.quarkus.arc.deployment.RecorderBeanInitializedBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.datasource.common.runtime.DatabaseKind; import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -59,10 +49,8 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.recording.RecorderContext; -import io.quarkus.hibernate.orm.deployment.HibernateConfigUtil; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit; -import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy; import io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor; import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem; import io.quarkus.hibernate.orm.deployment.PersistenceProviderSetUpBuildItem; @@ -280,7 +268,7 @@ private static QuarkusPersistenceUnitDescriptor generateReactivePersistenceUnit( BuildProducer hotDeploymentWatchedFiles, List dbKindDialectBuildItems) { //we have no persistence.xml so we will create a default one - String persistenceUnitConfigName = DEFAULT_PERSISTENCE_UNIT_NAME; + final String persistenceUnitConfigName = DEFAULT_PERSISTENCE_UNIT_NAME; Map> modelClassesAndPackagesPerPersistencesUnits = HibernateOrmProcessor .getModelClassesAndPackagesPerPersistenceUnits(hibernateOrmConfig, jpaModel, index.getIndex(), true); @@ -295,287 +283,36 @@ private static QuarkusPersistenceUnitDescriptor generateReactivePersistenceUnit( nonDefaultPUWithModelClassesOrPackages); } Set modelClassesAndPackages = modelClassesAndPackagesPerPersistencesUnits - .getOrDefault(DEFAULT_PERSISTENCE_UNIT_NAME, Collections.emptySet()); + .getOrDefault(persistenceUnitConfigName, Collections.emptySet()); if (modelClassesAndPackages.isEmpty()) { LOG.warnf("Could not find any entities affected to the Hibernate Reactive persistence unit."); } - QuarkusPersistenceUnitDescriptor desc = new QuarkusPersistenceUnitDescriptor( + QuarkusPersistenceUnitDescriptor descriptor = new QuarkusPersistenceUnitDescriptor( HibernateReactive.DEFAULT_REACTIVE_PERSISTENCE_UNIT_NAME, persistenceUnitConfigName, PersistenceUnitTransactionType.RESOURCE_LOCAL, new ArrayList<>(modelClassesAndPackages), new Properties(), true); - setDialectAndStorageEngine(dbKindOptional, explicitDialect, explicitDbMinVersion, dbKindDialectBuildItems, - persistenceUnitConfig.dialect().storageEngine(), systemProperties, desc); - - // Physical Naming Strategy - persistenceUnitConfig.physicalNamingStrategy().ifPresent( - namingStrategy -> desc.getProperties() - .setProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, namingStrategy)); - - // Implicit Naming Strategy - persistenceUnitConfig.implicitNamingStrategy().ifPresent( - namingStrategy -> desc.getProperties() - .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy)); - - // Mapping - if (persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().isPresent()) { - desc.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, - persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().get().name()); - } - desc.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, - persistenceUnitConfig.mapping().id().optimizer().idOptimizerDefault() - .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); - - //charset - desc.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, - persistenceUnitConfig.database().charset().name()); - - // Quoting strategy - if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL - || persistenceUnitConfig.quoteIdentifiers() - .strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS - || persistenceUnitConfig.database().globallyQuotedIdentifiers()) { - desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); - } - if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { - desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); - } else if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ONLY_KEYWORDS) { - desc.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); - } - - // Query - // TODO ideally we should align on ORM and use 16 as a default, but that would break applications - // because of https://github.com/hibernate/hibernate-reactive/issues/742 - int batchSize = firstPresent(persistenceUnitConfig.fetch().batchSize(), persistenceUnitConfig.batchFetchSize()) - .orElse(-1); - if (batchSize > 0) { - desc.getProperties().setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, - Integer.toString(batchSize)); - desc.getProperties().setProperty(AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.toString()); - } - - if (persistenceUnitConfig.fetch().maxDepth().isPresent()) { - setMaxFetchDepth(desc, persistenceUnitConfig.fetch().maxDepth()); - } else if (persistenceUnitConfig.maxFetchDepth().isPresent()) { - setMaxFetchDepth(desc, persistenceUnitConfig.maxFetchDepth()); - } - - desc.getProperties().setProperty(AvailableSettings.QUERY_PLAN_CACHE_MAX_SIZE, Integer.toString( - persistenceUnitConfig.query().queryPlanCacheMaxSize())); - - desc.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, - persistenceUnitConfig.query().defaultNullOrdering().name().toLowerCase()); - - desc.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, - String.valueOf(persistenceUnitConfig.query().inClauseParameterPadding())); - - // JDBC - persistenceUnitConfig.jdbc().timezone().ifPresent( - timezone -> desc.getProperties().setProperty(AvailableSettings.JDBC_TIME_ZONE, timezone)); - - persistenceUnitConfig.jdbc().statementFetchSize().ifPresent( - fetchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_FETCH_SIZE, - String.valueOf(fetchSize))); - - persistenceUnitConfig.jdbc().statementBatchSize().ifPresent( - statementBatchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, - String.valueOf(statementBatchSize))); - - // Statistics - if (hibernateOrmConfig.metrics().enabled() - || (hibernateOrmConfig.statistics().isPresent() && hibernateOrmConfig.statistics().get())) { - desc.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); - } - - // sql-load-script - List importFiles = getSqlLoadScript(persistenceUnitConfig.sqlLoadScript(), launchMode); - - if (!importFiles.isEmpty()) { - for (String importFile : importFiles) { - Path loadScriptPath = applicationArchivesBuildItem.getRootArchive().getChildPath(importFile); - - if (loadScriptPath != null && !Files.isDirectory(loadScriptPath)) { - // enlist resource if present - nativeImageResources.produce(new NativeImageResourceBuildItem(importFile)); - hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile)); - } else if (persistenceUnitConfig.sqlLoadScript().isPresent()) { - //raise exception if explicit file is not present (i.e. not the default) - String propertyName = HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitConfigName, "sql-load-script"); - throw new ConfigurationException( - "Unable to find file referenced in '" - + propertyName + "=" - + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) - + "'. Remove property or add file to your path.", - Collections.singleton(propertyName)); - } - } - - if (persistenceUnitConfig.sqlLoadScript().isPresent()) { - desc.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, String.join(",", importFiles)); - } - } else { - //Disable implicit loading of the default import script (import.sql) - desc.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, ""); - desc.getProperties().setProperty(AvailableSettings.HBM2DDL_SKIP_DEFAULT_IMPORT_FILE, "true"); - } - - // Caching - if (persistenceUnitConfig.secondLevelCachingEnabled()) { - Properties p = desc.getProperties(); - //Only set these if the user isn't making an explicit choice: - p.putIfAbsent(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.TRUE); - p.putIfAbsent(USE_SECOND_LEVEL_CACHE, Boolean.TRUE); - p.putIfAbsent(USE_QUERY_CACHE, Boolean.TRUE); - p.putIfAbsent(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE); - Map cacheConfigEntries = HibernateConfigUtil.getCacheConfigEntries(persistenceUnitConfig); - for (Entry entry : cacheConfigEntries.entrySet()) { - desc.getProperties().setProperty(entry.getKey(), entry.getValue()); - } - } else { - //Unless the global switch is explicitly set to off, in which case we disable all caching: - Properties p = desc.getProperties(); - p.put(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.FALSE); - p.put(USE_SECOND_LEVEL_CACHE, Boolean.FALSE); - p.put(USE_QUERY_CACHE, Boolean.FALSE); - p.put(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.NONE); - } - - if (!persistenceUnitConfig.validation().enabled()) { - desc.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name()); - } else { - desc.getProperties().setProperty( - AvailableSettings.JAKARTA_VALIDATION_MODE, - persistenceUnitConfig.validation().mode() - .stream() - .map(Enum::name) - .collect(Collectors.joining(","))); - } - - return desc; - } - - private static void setDialectAndStorageEngine(Optional dbKind, Optional explicitDialect, - Optional explicitDbMinVersion, List dbKindDialectBuildItems, - Optional storageEngine, BuildProducer systemProperties, - QuarkusPersistenceUnitDescriptor desc) { - final String persistenceUnitName = DEFAULT_PERSISTENCE_UNIT_NAME; - Optional dialect = explicitDialect; - Optional dbProductName = Optional.empty(); - Optional dbProductVersion = explicitDbMinVersion; - if (dbKind.isPresent() || explicitDialect.isPresent()) { - for (DatabaseKindDialectBuildItem item : dbKindDialectBuildItems) { - if (dbKind.isPresent() && DatabaseKind.is(dbKind.get(), item.getDbKind()) - // Set the default version based on the dialect when we don't have a datasource - // (i.e. for database multi-tenancy) - || explicitDialect.isPresent() && item.getMatchingDialects().contains(explicitDialect.get())) { - dbProductName = item.getDatabaseProductName(); - if (dbProductName.isEmpty() && explicitDialect.isEmpty()) { - // Use dialects only as a last resort, prefer product name or explicitly user-provided dialect - dialect = item.getDialectOptional(); - } - if (explicitDbMinVersion.isEmpty()) { - dbProductVersion = item.getDefaultDatabaseProductVersion(); - } - break; - } - } - if (dialect.isEmpty() && dbProductName.isEmpty()) { - throw new ConfigurationException( - "The Hibernate Reactive extension could not guess the dialect from the database kind '" - + dbKind.get() - + "'. Add an explicit '" - + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") - + "' property."); - } - } - - if (dialect.isPresent()) { - desc.getProperties().setProperty(AvailableSettings.DIALECT, dialect.get()); - } else if (dbProductName.isPresent()) { - desc.getProperties().setProperty(AvailableSettings.JAKARTA_HBM2DDL_DB_NAME, dbProductName.get()); - } else { - // We only get here with the database multi-tenancy strategy; see the initial check, up top. - throw new ConfigurationException(String.format(Locale.ROOT, - "The Hibernate Reactive extension could not infer the dialect for persistence unit '%s'." - + " When using database multi-tenancy, you must either configure a datasource for that persistence unit" - + " (refer to https://quarkus.io/guides/datasource for guidance)," - + " or set the dialect explicitly through property '" - + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + "'.", - persistenceUnitName)); - } - - // The storage engine has to be set as a system property. - if (storageEngine.isPresent()) { - systemProperties.produce(new SystemPropertyBuildItem(STORAGE_ENGINE, storageEngine.get())); - // Only actually set the storage engines if MySQL or MariaDB - if (isMySQLOrMariaDB(dbKind, dialect)) { - systemProperties.produce(new SystemPropertyBuildItem(STORAGE_ENGINE, storageEngine.get())); - } else { - LOG.warnf("The storage engine set through configuration property '%1$s' is being ignored" - + " because the database is neither MySQL nor MariaDB.", - HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect.storage-engine")); - } - } - - if (dbProductVersion.isPresent()) { - desc.getProperties().setProperty(JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get()); - } - } - - private static boolean isMySQLOrMariaDB(Optional dbKind, Optional dialect) { - if (dbKind.isPresent() && (DatabaseKind.isMySQL(dbKind.get()) || DatabaseKind.isMariaDB(dbKind.get()))) { - return true; - } - if (dialect.isPresent()) { - String lowercaseDialect = dialect.get().toLowerCase(Locale.ROOT); - return lowercaseDialect.contains("mysql") || lowercaseDialect.contains("mariadb"); - } - return false; - } - - private static void setMaxFetchDepth(QuarkusPersistenceUnitDescriptor descriptor, OptionalInt maxFetchDepth) { - descriptor.getProperties().setProperty(AvailableSettings.MAX_FETCH_DEPTH, String.valueOf(maxFetchDepth.getAsInt())); - } + Set storageEngineCollector = new HashSet<>(); - private static List getSqlLoadScript(Optional> sqlLoadScript, LaunchMode launchMode) { - // Explicit file or default Hibernate ORM file. - if (sqlLoadScript.isPresent()) { - return sqlLoadScript.get().stream() - .filter(s -> !HibernateOrmProcessor.NO_SQL_LOAD_SCRIPT_FILE.equalsIgnoreCase(s)) - .collect(Collectors.toList()); - } else if (launchMode == LaunchMode.NORMAL) { - return Collections.emptyList(); - } else { - return List.of("import.sql"); - } - } + setDialectAndStorageEngine( + persistenceUnitConfigName, + dbKindOptional, + explicitDialect, + explicitDbMinVersion, + dbKindDialectBuildItems, + persistenceUnitConfig.dialect().storageEngine(), + systemProperties, + descriptor.getProperties()::setProperty, + storageEngineCollector); - private boolean hasEntities(JpaModelBuildItem jpaModel) { - return !jpaModel.getEntityClassNames().isEmpty(); - } - - private static Optional jsonMapperKind(Capabilities capabilities) { - if (capabilities.isPresent(Capability.JACKSON)) { - return Optional.of(FormatMapperKind.JACKSON); - } - if (capabilities.isPresent(Capability.JSONB)) { - return Optional.of(FormatMapperKind.JSONB); - } - return Optional.empty(); - } - - private static Optional xmlMapperKind(Capabilities capabilities) { - if (capabilities.isPresent(Capability.JAXB)) { - return Optional.of(FormatMapperKind.JAXB); - } - return Optional.empty(); - } + configureProperties(descriptor, persistenceUnitConfig, hibernateOrmConfig); + configureSqlLoadScript(persistenceUnitConfigName, persistenceUnitConfig, applicationArchivesBuildItem, launchMode, + nativeImageResources, hotDeploymentWatchedFiles, descriptor); - private static boolean isHibernateValidatorPresent(Capabilities capabilities) { - return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR); + return descriptor; } } From 9b2d071f9bfe2ad0a96b6c0d1c3ae0a65a5dad22 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 4 Jun 2025 12:32:51 +0200 Subject: [PATCH 3/3] Revert "[#31974][#28629] Refactor the Hibernate processors" This reverts commit e6ffaf68ea4df6ed1ecf218f296e8dc6e6ee2b52. --- .../deployment/HibernateOrmCdiProcessor.java | 5 +- .../orm/deployment/HibernateOrmProcessor.java | 331 ++++++++++++++++-- .../HibernateReactiveProcessor.java | 317 +++++++++++++++-- 3 files changed, 597 insertions(+), 56 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index 282c05f8a4e01..4f4a9f02e3982 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.hibernate.orm.deployment; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.hasEntities; import static org.apache.commons.lang3.BooleanUtils.isFalse; import java.lang.reflect.Modifier; @@ -265,7 +264,7 @@ void registerBeans(HibernateOrmConfig hibernateOrmConfig, CombinedIndexBuildItem combinedIndex, List descriptors, JpaModelBuildItem jpaModel) { - if (!hasEntities(jpaModel)) { + if (!HibernateOrmProcessor.hasEntities(jpaModel)) { return; } @@ -294,7 +293,7 @@ void registerBeans(HibernateOrmConfig hibernateOrmConfig, void transformBeans(JpaModelBuildItem jpaModel, JpaModelIndexBuildItem indexBuildItem, BeanDiscoveryFinishedBuildItem beans, BuildProducer producer) { - if (!hasEntities(jpaModel)) { + if (!HibernateOrmProcessor.hasEntities(jpaModel)) { return; } 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 ad5cb77d146e3..33513518cc1ba 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 @@ -2,16 +2,16 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureProperties; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureSqlLoadScript; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.hasEntities; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.isHibernateValidatorPresent; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.jsonMapperKind; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.setDialectAndStorageEngine; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.xmlMapperKind; +import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent; +import static org.hibernate.cfg.CacheSettings.JAKARTA_SHARED_CACHE_MODE; +import static org.hibernate.cfg.CacheSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES; +import static org.hibernate.cfg.CacheSettings.USE_QUERY_CACHE; +import static org.hibernate.cfg.CacheSettings.USE_SECOND_LEVEL_CACHE; import java.io.IOException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -36,6 +36,8 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; +import jakarta.persistence.SharedCacheMode; +import jakarta.persistence.ValidationMode; import jakarta.persistence.spi.PersistenceUnitTransactionType; import jakarta.xml.bind.JAXBElement; @@ -43,8 +45,12 @@ import org.hibernate.boot.archive.scan.spi.PackageDescriptor; import org.hibernate.boot.beanvalidation.BeanValidationIntegrator; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.id.SequenceMismatchStrategy; import org.hibernate.integrator.spi.Integrator; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.jpa.boot.spi.JpaSettings; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.loader.BatchFetchStyle; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.AnnotationValue; @@ -106,6 +112,7 @@ import io.quarkus.deployment.util.IoUtil; import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.hibernate.orm.PersistenceUnit; +import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationStaticConfiguredBuildItem; import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem; @@ -152,6 +159,7 @@ public final class HibernateOrmProcessor { public static final String HIBERNATE_ORM_CONFIG_PREFIX = "quarkus.hibernate-orm."; + public static final String NO_SQL_LOAD_SCRIPT_FILE = "no-file"; private static final Logger LOG = Logger.getLogger(HibernateOrmProcessor.class); @@ -794,6 +802,23 @@ public void registerInjectServiceMethodsForReflection(CombinedIndexBuildItem ind } } + private static List getSqlLoadScript(Optional> sqlLoadScript, LaunchMode launchMode) { + // Explicit file or default Hibernate ORM file. + if (sqlLoadScript.isPresent()) { + return sqlLoadScript.get().stream() + .filter(s -> !NO_SQL_LOAD_SCRIPT_FILE.equalsIgnoreCase(s)) + .collect(Collectors.toList()); + } else if (launchMode == LaunchMode.NORMAL) { + return Collections.emptyList(); + } else { + return List.of("import.sql"); + } + } + + static boolean hasEntities(JpaModelBuildItem jpaModel) { + return !jpaModel.getEntityClassNames().isEmpty(); + } + private void handleHibernateORMWithNoPersistenceXml( HibernateOrmConfig hibernateOrmConfig, CombinedIndexBuildItem index, @@ -935,15 +960,179 @@ private static void producePersistenceUnitDescriptorFromConfig( false); MultiTenancyStrategy multiTenancyStrategy = getMultiTenancyStrategy(persistenceUnitConfig.multitenant()); - collectDialectConfig(persistenceUnitName, persistenceUnitConfig, dbKindMetadataBuildItems, jdbcDataSource, multiTenancyStrategy, systemProperties, reflectiveMethods, descriptor.getProperties()::setProperty, storageEngineCollector); - configureProperties(descriptor, persistenceUnitConfig, hibernateOrmConfig); + // Physical Naming Strategy + persistenceUnitConfig.physicalNamingStrategy().ifPresent( + namingStrategy -> descriptor.getProperties() + .setProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, namingStrategy)); + + // Implicit Naming Strategy + persistenceUnitConfig.implicitNamingStrategy().ifPresent( + namingStrategy -> descriptor.getProperties() + .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy)); + + // Metadata builder contributor + persistenceUnitConfig.metadataBuilderContributor().ifPresent( + className -> descriptor.getProperties() + .setProperty(JpaSettings.METADATA_BUILDER_CONTRIBUTOR, className)); + + // Mapping + if (persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().isPresent()) { + descriptor.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, + persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().get().name()); + } + descriptor.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, + persistenceUnitConfig.mapping().id().optimizer().idOptimizerDefault() + .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); + + //charset + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, + persistenceUnitConfig.database().charset().name()); + + // Quoting strategy + if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL + || persistenceUnitConfig.quoteIdentifiers() + .strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS + || persistenceUnitConfig.database().globallyQuotedIdentifiers()) { + descriptor.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); + } + if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { + descriptor.getProperties().setProperty( + AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); + } else if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ONLY_KEYWORDS) { + descriptor.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); + } + + // Query + int batchSize = firstPresent(persistenceUnitConfig.fetch().batchSize(), persistenceUnitConfig.batchFetchSize()) + .orElse(16); + if (batchSize > 0) { + descriptor.getProperties().setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, + Integer.toString(batchSize)); + descriptor.getProperties().setProperty(AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.toString()); + } + + if (persistenceUnitConfig.fetch().maxDepth().isPresent()) { + setMaxFetchDepth(descriptor, persistenceUnitConfig.fetch().maxDepth()); + } else if (persistenceUnitConfig.maxFetchDepth().isPresent()) { + setMaxFetchDepth(descriptor, persistenceUnitConfig.maxFetchDepth()); + } + + descriptor.getProperties().setProperty(AvailableSettings.QUERY_PLAN_CACHE_MAX_SIZE, Integer.toString( + persistenceUnitConfig.query().queryPlanCacheMaxSize())); + + descriptor.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, + persistenceUnitConfig.query().defaultNullOrdering().name().toLowerCase(Locale.ROOT)); + + descriptor.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, + String.valueOf(persistenceUnitConfig.query().inClauseParameterPadding())); + + // Disable sequence validations: they are reportedly slow, and people already get the same validation from normal schema validation + descriptor.getProperties().put(AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY, + SequenceMismatchStrategy.NONE); + + // JDBC + persistenceUnitConfig.jdbc().timezone().ifPresent( + timezone -> descriptor.getProperties().setProperty(AvailableSettings.JDBC_TIME_ZONE, timezone)); + + persistenceUnitConfig.jdbc().statementFetchSize().ifPresent( + fetchSize -> descriptor.getProperties().setProperty(AvailableSettings.STATEMENT_FETCH_SIZE, + String.valueOf(fetchSize))); + + persistenceUnitConfig.jdbc().statementBatchSize().ifPresent( + fetchSize -> descriptor.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, + String.valueOf(fetchSize))); + + // Statistics + if (hibernateOrmConfig.metrics().enabled() + || (hibernateOrmConfig.statistics().isPresent() && hibernateOrmConfig.statistics().get())) { + descriptor.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); + //When statistics are enabled, the default in Hibernate ORM is to also log them after each + // session; turn that off by default as it's very noisy: + descriptor.getProperties().setProperty(AvailableSettings.LOG_SESSION_METRICS, + String.valueOf(hibernateOrmConfig.logSessionMetrics().orElse(false))); + } + + // sql-load-scripts + List importFiles = getSqlLoadScript(persistenceUnitConfig.sqlLoadScript(), launchMode); + + if (!importFiles.isEmpty()) { + for (String importFile : importFiles) { + Path loadScriptPath; + try { + loadScriptPath = applicationArchivesBuildItem.getRootArchive().getChildPath(importFile); + } catch (RuntimeException e) { + throw new ConfigurationException( + "Unable to interpret path referenced in '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "=" + + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) + + "': " + e.getMessage()); + } - configureSqlLoadScript(persistenceUnitName, persistenceUnitConfig, applicationArchivesBuildItem, launchMode, - nativeImageResources, hotDeploymentWatchedFiles, descriptor); + if (loadScriptPath != null && !Files.isDirectory(loadScriptPath)) { + // enlist resource if present + nativeImageResources.produce(new NativeImageResourceBuildItem(importFile)); + } else if (persistenceUnitConfig.sqlLoadScript().isPresent()) { + //raise exception if explicit file is not present (i.e. not the default) + throw new ConfigurationException( + "Unable to find file referenced in '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "=" + + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) + + "'. Remove property or add file to your path."); + } + // in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist + // as a user could still add it after performing the initial configuration + hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile)); + } + + // only set the found import files if configured + if (persistenceUnitConfig.sqlLoadScript().isPresent()) { + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, String.join(",", importFiles)); + } + } else { + //Disable implicit loading of the default import script (import.sql) + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, ""); + descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_SKIP_DEFAULT_IMPORT_FILE, "true"); + } + + // Caching + if (persistenceUnitConfig.secondLevelCachingEnabled()) { + Properties p = descriptor.getProperties(); + //Only set these if the user isn't making an explicit choice: + p.putIfAbsent(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.TRUE); + p.putIfAbsent(USE_SECOND_LEVEL_CACHE, Boolean.TRUE); + p.putIfAbsent(USE_QUERY_CACHE, Boolean.TRUE); + p.putIfAbsent(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE); + Map cacheConfigEntries = HibernateConfigUtil.getCacheConfigEntries(persistenceUnitConfig); + for (Entry entry : cacheConfigEntries.entrySet()) { + descriptor.getProperties().setProperty(entry.getKey(), entry.getValue()); + } + } else { + //Unless the global switch is explicitly set to off, in which case we disable all caching: + Properties p = descriptor.getProperties(); + p.put(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.FALSE); + p.put(USE_SECOND_LEVEL_CACHE, Boolean.FALSE); + p.put(USE_QUERY_CACHE, Boolean.FALSE); + p.put(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.NONE); + } + + if (!persistenceUnitConfig.validation().enabled()) { + descriptor.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name()); + } else { + descriptor.getProperties().setProperty( + AvailableSettings.JAKARTA_VALIDATION_MODE, + persistenceUnitConfig.validation().mode() + .stream() + .map(Enum::name) + .collect(Collectors.joining(","))); + } + + // Discriminator Column + descriptor.getProperties().setProperty(AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS, + String.valueOf(persistenceUnitConfig.discriminator().ignoreExplicitForJoined())); Optional jsonMapper = jsonMapperKind(capabilities); Optional xmlMapper = xmlMapperKind(capabilities); @@ -969,14 +1158,12 @@ private static void producePersistenceUnitDescriptorFromConfig( private static void collectDialectConfig(String persistenceUnitName, HibernateOrmConfigPersistenceUnit persistenceUnitConfig, - List dbKindMetadataBuildItems, - Optional jdbcDataSource, + List dbKindMetadataBuildItems, Optional jdbcDataSource, MultiTenancyStrategy multiTenancyStrategy, BuildProducer systemProperties, BuildProducer reflectiveMethods, - BiConsumer puPropertiesCollector, - Set storageEngineCollector) { - Optional dialect = persistenceUnitConfig.dialect().dialect(); + BiConsumer puPropertiesCollector, Set storageEngineCollector) { + Optional explicitDialect = persistenceUnitConfig.dialect().dialect(); Optional dbKind = jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind); Optional explicitDbMinVersion = jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion); if (multiTenancyStrategy != MultiTenancyStrategy.DATABASE && jdbcDataSource.isEmpty()) { @@ -988,16 +1175,69 @@ private static void collectDialectConfig(String persistenceUnitName, "quarkus.datasource.password", "quarkus.datasource.jdbc.url"))); } - setDialectAndStorageEngine( - persistenceUnitName, - dbKind, - dialect, - explicitDbMinVersion, - dbKindMetadataBuildItems, - persistenceUnitConfig.dialect().storageEngine(), - systemProperties, - puPropertiesCollector, - storageEngineCollector); + Optional dialect = explicitDialect; + Optional dbProductName = Optional.empty(); + Optional dbProductVersion = explicitDbMinVersion; + if (dbKind.isPresent() || explicitDialect.isPresent()) { + for (DatabaseKindDialectBuildItem item : dbKindMetadataBuildItems) { + if (dbKind.isPresent() && DatabaseKind.is(dbKind.get(), item.getDbKind()) + // Set the default version based on the dialect when we don't have a datasource + // (i.e. for database multi-tenancy) + || explicitDialect.isPresent() && item.getMatchingDialects().contains(explicitDialect.get())) { + dbProductName = item.getDatabaseProductName(); + if (dbProductName.isEmpty() && explicitDialect.isEmpty()) { + // Use dialects only as a last resort, prefer product name or explicitly user-provided dialect + dialect = item.getDialectOptional(); + } + if (explicitDbMinVersion.isEmpty()) { + dbProductVersion = item.getDefaultDatabaseProductVersion(); + } + break; + } + } + if (dialect.isEmpty() && dbProductName.isEmpty()) { + throw new ConfigurationException( + "The Hibernate ORM extension could not guess the dialect from the database kind '" + dbKind.get() + + "'. Add an explicit '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + + "' property."); + } + } + + if (dialect.isPresent()) { + puPropertiesCollector.accept(AvailableSettings.DIALECT, dialect.get()); + } else if (dbProductName.isPresent()) { + puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_NAME, dbProductName.get()); + } else { + // We only get here with the database multi-tenancy strategy; see the initial check, up top. + assert multiTenancyStrategy == MultiTenancyStrategy.DATABASE; + throw new ConfigurationException(String.format(Locale.ROOT, + "The Hibernate ORM extension could not infer the dialect for persistence unit '%s'." + + " When using database multi-tenancy, you must either configure a datasource for that persistence unit" + + " (refer to https://quarkus.io/guides/datasource for guidance)," + + " or set the dialect explicitly through property '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + "'.", + persistenceUnitName)); + } + + if (persistenceUnitConfig.dialect().storageEngine().isPresent()) { + // Only actually set the storage engines if MySQL or MariaDB + if (isMySQLOrMariaDB(dbKind, dialect)) { + // The storage engine has to be set as a system property. + // We record it so that we can later run checks (because we can only set a single value) + storageEngineCollector.add(persistenceUnitConfig.dialect().storageEngine().get()); + systemProperties.produce(new SystemPropertyBuildItem(AvailableSettings.STORAGE_ENGINE, + persistenceUnitConfig.dialect().storageEngine().get())); + } else { + LOG.warnf("The storage engine set through configuration property '%1$s' is being ignored" + + " because the database is neither MySQL nor MariaDB.", + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect.storage-engine")); + } + } + + if (dbProductVersion.isPresent()) { + puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get()); + } if ((dbKind.isPresent() && DatabaseKind.isPostgreSQL(dbKind.get()) || (dialect.isPresent() && dialect.get().toLowerCase(Locale.ROOT).contains("postgres")))) { @@ -1444,6 +1684,45 @@ private boolean isModified(String entity, Set changedClasses, IndexView return false; } + private static Class[] toArray(final Set> interfaces) { + if (interfaces == null) { + return ArrayHelper.EMPTY_CLASS_ARRAY; + } + return interfaces.toArray(new Class[interfaces.size()]); + } + + private static boolean isMySQLOrMariaDB(Optional dbKind, Optional dialect) { + if (dbKind.isPresent() && (DatabaseKind.isMySQL(dbKind.get()) || DatabaseKind.isMariaDB(dbKind.get()))) { + return true; + } + if (dialect.isPresent()) { + String lowercaseDialect = dialect.get().toLowerCase(Locale.ROOT); + return lowercaseDialect.contains("mysql") || lowercaseDialect.contains("mariadb"); + } + return false; + } + + private static Optional jsonMapperKind(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JACKSON)) { + return Optional.of(FormatMapperKind.JACKSON); + } + if (capabilities.isPresent(Capability.JSONB)) { + return Optional.of(FormatMapperKind.JSONB); + } + return Optional.empty(); + } + + private static Optional xmlMapperKind(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JAXB)) { + return Optional.of(FormatMapperKind.JAXB); + } + return Optional.empty(); + } + + private static boolean isHibernateValidatorPresent(Capabilities capabilities) { + return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR); + } + private static final class ProxyCache { Map cache = new HashMap<>(); 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 b9e83a00a928d..479f1edbf3356 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 @@ -2,38 +2,48 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureProperties; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.configureSqlLoadScript; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.hasEntities; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.isHibernateValidatorPresent; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.jsonMapperKind; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.setDialectAndStorageEngine; -import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.xmlMapperKind; +import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent; import static io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME; - +import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION; +import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_MODE; +import static org.hibernate.cfg.AvailableSettings.STORAGE_ENGINE; +import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES; +import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE; +import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE; + +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; +import java.util.OptionalInt; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; +import jakarta.persistence.SharedCacheMode; +import jakarta.persistence.ValidationMode; import jakarta.persistence.spi.PersistenceUnitTransactionType; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.loader.BatchFetchStyle; import org.hibernate.reactive.provider.impl.ReactiveIntegrator; import org.jboss.logging.Logger; import io.quarkus.arc.deployment.RecorderBeanInitializedBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.datasource.common.runtime.DatabaseKind; import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -49,8 +59,10 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.recording.RecorderContext; +import io.quarkus.hibernate.orm.deployment.HibernateConfigUtil; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit; +import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy; import io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor; import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem; import io.quarkus.hibernate.orm.deployment.PersistenceProviderSetUpBuildItem; @@ -268,7 +280,7 @@ private static QuarkusPersistenceUnitDescriptor generateReactivePersistenceUnit( BuildProducer hotDeploymentWatchedFiles, List dbKindDialectBuildItems) { //we have no persistence.xml so we will create a default one - final String persistenceUnitConfigName = DEFAULT_PERSISTENCE_UNIT_NAME; + String persistenceUnitConfigName = DEFAULT_PERSISTENCE_UNIT_NAME; Map> modelClassesAndPackagesPerPersistencesUnits = HibernateOrmProcessor .getModelClassesAndPackagesPerPersistenceUnits(hibernateOrmConfig, jpaModel, index.getIndex(), true); @@ -283,36 +295,287 @@ private static QuarkusPersistenceUnitDescriptor generateReactivePersistenceUnit( nonDefaultPUWithModelClassesOrPackages); } Set modelClassesAndPackages = modelClassesAndPackagesPerPersistencesUnits - .getOrDefault(persistenceUnitConfigName, Collections.emptySet()); + .getOrDefault(DEFAULT_PERSISTENCE_UNIT_NAME, Collections.emptySet()); if (modelClassesAndPackages.isEmpty()) { LOG.warnf("Could not find any entities affected to the Hibernate Reactive persistence unit."); } - QuarkusPersistenceUnitDescriptor descriptor = new QuarkusPersistenceUnitDescriptor( + QuarkusPersistenceUnitDescriptor desc = new QuarkusPersistenceUnitDescriptor( HibernateReactive.DEFAULT_REACTIVE_PERSISTENCE_UNIT_NAME, persistenceUnitConfigName, PersistenceUnitTransactionType.RESOURCE_LOCAL, new ArrayList<>(modelClassesAndPackages), new Properties(), true); - Set storageEngineCollector = new HashSet<>(); + setDialectAndStorageEngine(dbKindOptional, explicitDialect, explicitDbMinVersion, dbKindDialectBuildItems, + persistenceUnitConfig.dialect().storageEngine(), systemProperties, desc); + + // Physical Naming Strategy + persistenceUnitConfig.physicalNamingStrategy().ifPresent( + namingStrategy -> desc.getProperties() + .setProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, namingStrategy)); + + // Implicit Naming Strategy + persistenceUnitConfig.implicitNamingStrategy().ifPresent( + namingStrategy -> desc.getProperties() + .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy)); + + // Mapping + if (persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().isPresent()) { + desc.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, + persistenceUnitConfig.mapping().timezone().timeZoneDefaultStorage().get().name()); + } + desc.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, + persistenceUnitConfig.mapping().id().optimizer().idOptimizerDefault() + .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); + + //charset + desc.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, + persistenceUnitConfig.database().charset().name()); + + // Quoting strategy + if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL + || persistenceUnitConfig.quoteIdentifiers() + .strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS + || persistenceUnitConfig.database().globallyQuotedIdentifiers()) { + desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); + } + if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { + desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); + } else if (persistenceUnitConfig.quoteIdentifiers().strategy() == IdentifierQuotingStrategy.ONLY_KEYWORDS) { + desc.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); + } + + // Query + // TODO ideally we should align on ORM and use 16 as a default, but that would break applications + // because of https://github.com/hibernate/hibernate-reactive/issues/742 + int batchSize = firstPresent(persistenceUnitConfig.fetch().batchSize(), persistenceUnitConfig.batchFetchSize()) + .orElse(-1); + if (batchSize > 0) { + desc.getProperties().setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, + Integer.toString(batchSize)); + desc.getProperties().setProperty(AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.toString()); + } + + if (persistenceUnitConfig.fetch().maxDepth().isPresent()) { + setMaxFetchDepth(desc, persistenceUnitConfig.fetch().maxDepth()); + } else if (persistenceUnitConfig.maxFetchDepth().isPresent()) { + setMaxFetchDepth(desc, persistenceUnitConfig.maxFetchDepth()); + } + + desc.getProperties().setProperty(AvailableSettings.QUERY_PLAN_CACHE_MAX_SIZE, Integer.toString( + persistenceUnitConfig.query().queryPlanCacheMaxSize())); + + desc.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, + persistenceUnitConfig.query().defaultNullOrdering().name().toLowerCase()); + + desc.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, + String.valueOf(persistenceUnitConfig.query().inClauseParameterPadding())); + + // JDBC + persistenceUnitConfig.jdbc().timezone().ifPresent( + timezone -> desc.getProperties().setProperty(AvailableSettings.JDBC_TIME_ZONE, timezone)); + + persistenceUnitConfig.jdbc().statementFetchSize().ifPresent( + fetchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_FETCH_SIZE, + String.valueOf(fetchSize))); + + persistenceUnitConfig.jdbc().statementBatchSize().ifPresent( + statementBatchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, + String.valueOf(statementBatchSize))); + + // Statistics + if (hibernateOrmConfig.metrics().enabled() + || (hibernateOrmConfig.statistics().isPresent() && hibernateOrmConfig.statistics().get())) { + desc.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); + } + + // sql-load-script + List importFiles = getSqlLoadScript(persistenceUnitConfig.sqlLoadScript(), launchMode); + + if (!importFiles.isEmpty()) { + for (String importFile : importFiles) { + Path loadScriptPath = applicationArchivesBuildItem.getRootArchive().getChildPath(importFile); + + if (loadScriptPath != null && !Files.isDirectory(loadScriptPath)) { + // enlist resource if present + nativeImageResources.produce(new NativeImageResourceBuildItem(importFile)); + hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile)); + } else if (persistenceUnitConfig.sqlLoadScript().isPresent()) { + //raise exception if explicit file is not present (i.e. not the default) + String propertyName = HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitConfigName, "sql-load-script"); + throw new ConfigurationException( + "Unable to find file referenced in '" + + propertyName + "=" + + String.join(",", persistenceUnitConfig.sqlLoadScript().get()) + + "'. Remove property or add file to your path.", + Collections.singleton(propertyName)); + } + } + + if (persistenceUnitConfig.sqlLoadScript().isPresent()) { + desc.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, String.join(",", importFiles)); + } + } else { + //Disable implicit loading of the default import script (import.sql) + desc.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, ""); + desc.getProperties().setProperty(AvailableSettings.HBM2DDL_SKIP_DEFAULT_IMPORT_FILE, "true"); + } + + // Caching + if (persistenceUnitConfig.secondLevelCachingEnabled()) { + Properties p = desc.getProperties(); + //Only set these if the user isn't making an explicit choice: + p.putIfAbsent(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.TRUE); + p.putIfAbsent(USE_SECOND_LEVEL_CACHE, Boolean.TRUE); + p.putIfAbsent(USE_QUERY_CACHE, Boolean.TRUE); + p.putIfAbsent(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE); + Map cacheConfigEntries = HibernateConfigUtil.getCacheConfigEntries(persistenceUnitConfig); + for (Entry entry : cacheConfigEntries.entrySet()) { + desc.getProperties().setProperty(entry.getKey(), entry.getValue()); + } + } else { + //Unless the global switch is explicitly set to off, in which case we disable all caching: + Properties p = desc.getProperties(); + p.put(USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.FALSE); + p.put(USE_SECOND_LEVEL_CACHE, Boolean.FALSE); + p.put(USE_QUERY_CACHE, Boolean.FALSE); + p.put(JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.NONE); + } + + if (!persistenceUnitConfig.validation().enabled()) { + desc.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name()); + } else { + desc.getProperties().setProperty( + AvailableSettings.JAKARTA_VALIDATION_MODE, + persistenceUnitConfig.validation().mode() + .stream() + .map(Enum::name) + .collect(Collectors.joining(","))); + } + + return desc; + } + + private static void setDialectAndStorageEngine(Optional dbKind, Optional explicitDialect, + Optional explicitDbMinVersion, List dbKindDialectBuildItems, + Optional storageEngine, BuildProducer systemProperties, + QuarkusPersistenceUnitDescriptor desc) { + final String persistenceUnitName = DEFAULT_PERSISTENCE_UNIT_NAME; + Optional dialect = explicitDialect; + Optional dbProductName = Optional.empty(); + Optional dbProductVersion = explicitDbMinVersion; + if (dbKind.isPresent() || explicitDialect.isPresent()) { + for (DatabaseKindDialectBuildItem item : dbKindDialectBuildItems) { + if (dbKind.isPresent() && DatabaseKind.is(dbKind.get(), item.getDbKind()) + // Set the default version based on the dialect when we don't have a datasource + // (i.e. for database multi-tenancy) + || explicitDialect.isPresent() && item.getMatchingDialects().contains(explicitDialect.get())) { + dbProductName = item.getDatabaseProductName(); + if (dbProductName.isEmpty() && explicitDialect.isEmpty()) { + // Use dialects only as a last resort, prefer product name or explicitly user-provided dialect + dialect = item.getDialectOptional(); + } + if (explicitDbMinVersion.isEmpty()) { + dbProductVersion = item.getDefaultDatabaseProductVersion(); + } + break; + } + } + if (dialect.isEmpty() && dbProductName.isEmpty()) { + throw new ConfigurationException( + "The Hibernate Reactive extension could not guess the dialect from the database kind '" + + dbKind.get() + + "'. Add an explicit '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + + "' property."); + } + } + + if (dialect.isPresent()) { + desc.getProperties().setProperty(AvailableSettings.DIALECT, dialect.get()); + } else if (dbProductName.isPresent()) { + desc.getProperties().setProperty(AvailableSettings.JAKARTA_HBM2DDL_DB_NAME, dbProductName.get()); + } else { + // We only get here with the database multi-tenancy strategy; see the initial check, up top. + throw new ConfigurationException(String.format(Locale.ROOT, + "The Hibernate Reactive extension could not infer the dialect for persistence unit '%s'." + + " When using database multi-tenancy, you must either configure a datasource for that persistence unit" + + " (refer to https://quarkus.io/guides/datasource for guidance)," + + " or set the dialect explicitly through property '" + + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect") + "'.", + persistenceUnitName)); + } + + // The storage engine has to be set as a system property. + if (storageEngine.isPresent()) { + systemProperties.produce(new SystemPropertyBuildItem(STORAGE_ENGINE, storageEngine.get())); + // Only actually set the storage engines if MySQL or MariaDB + if (isMySQLOrMariaDB(dbKind, dialect)) { + systemProperties.produce(new SystemPropertyBuildItem(STORAGE_ENGINE, storageEngine.get())); + } else { + LOG.warnf("The storage engine set through configuration property '%1$s' is being ignored" + + " because the database is neither MySQL nor MariaDB.", + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect.storage-engine")); + } + } + + if (dbProductVersion.isPresent()) { + desc.getProperties().setProperty(JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get()); + } + } + + private static boolean isMySQLOrMariaDB(Optional dbKind, Optional dialect) { + if (dbKind.isPresent() && (DatabaseKind.isMySQL(dbKind.get()) || DatabaseKind.isMariaDB(dbKind.get()))) { + return true; + } + if (dialect.isPresent()) { + String lowercaseDialect = dialect.get().toLowerCase(Locale.ROOT); + return lowercaseDialect.contains("mysql") || lowercaseDialect.contains("mariadb"); + } + return false; + } + + private static void setMaxFetchDepth(QuarkusPersistenceUnitDescriptor descriptor, OptionalInt maxFetchDepth) { + descriptor.getProperties().setProperty(AvailableSettings.MAX_FETCH_DEPTH, String.valueOf(maxFetchDepth.getAsInt())); + } - setDialectAndStorageEngine( - persistenceUnitConfigName, - dbKindOptional, - explicitDialect, - explicitDbMinVersion, - dbKindDialectBuildItems, - persistenceUnitConfig.dialect().storageEngine(), - systemProperties, - descriptor.getProperties()::setProperty, - storageEngineCollector); + private static List getSqlLoadScript(Optional> sqlLoadScript, LaunchMode launchMode) { + // Explicit file or default Hibernate ORM file. + if (sqlLoadScript.isPresent()) { + return sqlLoadScript.get().stream() + .filter(s -> !HibernateOrmProcessor.NO_SQL_LOAD_SCRIPT_FILE.equalsIgnoreCase(s)) + .collect(Collectors.toList()); + } else if (launchMode == LaunchMode.NORMAL) { + return Collections.emptyList(); + } else { + return List.of("import.sql"); + } + } - configureProperties(descriptor, persistenceUnitConfig, hibernateOrmConfig); - configureSqlLoadScript(persistenceUnitConfigName, persistenceUnitConfig, applicationArchivesBuildItem, launchMode, - nativeImageResources, hotDeploymentWatchedFiles, descriptor); + private boolean hasEntities(JpaModelBuildItem jpaModel) { + return !jpaModel.getEntityClassNames().isEmpty(); + } + + private static Optional jsonMapperKind(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JACKSON)) { + return Optional.of(FormatMapperKind.JACKSON); + } + if (capabilities.isPresent(Capability.JSONB)) { + return Optional.of(FormatMapperKind.JSONB); + } + return Optional.empty(); + } + + private static Optional xmlMapperKind(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JAXB)) { + return Optional.of(FormatMapperKind.JAXB); + } + return Optional.empty(); + } - return descriptor; + private static boolean isHibernateValidatorPresent(Capabilities capabilities) { + return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR); } }