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 fbc3f973e89539..7b609f488a5c53 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 @@ -48,6 +48,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.hibernate.orm.PersistenceUnit; +import io.quarkus.hibernate.orm.ReactivePersistenceUnit; import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder; import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; import io.quarkus.hibernate.orm.runtime.JPAConfig; @@ -222,11 +223,21 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder, String persistenceUnitConfigName = persistenceUnitDescriptor.getConfigurationName(); boolean isDefaultPU = PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitConfigName); boolean isNamedPU = !isDefaultPU; + boolean isReactive = persistenceUnitDescriptor.isReactive(); + + ExtendedBeanConfigurator persistenceUnitBean = createSyntheticBean(persistenceUnitName, + isDefaultPU, + isNamedPU, + SessionFactory.class, + SESSION_FACTORY_EXPOSED_TYPES, + true); + + if (isReactive) { + persistenceUnitBean.addQualifier().annotation(ReactivePersistenceUnit.class).done(); + } syntheticBeanBuildItemBuildProducer - .produce(createSyntheticBean(persistenceUnitName, - isDefaultPU, isNamedPU, - SessionFactory.class, SESSION_FACTORY_EXPOSED_TYPES, true) + .produce(persistenceUnitBean .createWith(recorder.sessionFactorySupplier(persistenceUnitName)) .addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class))) .done()); 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 48cdaee3dcf35b..0fa8a2a365bed3 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 @@ -285,16 +285,7 @@ public void parsePersistenceXmlDescriptors(HibernateOrmConfig config, public ImpliedBlockingPersistenceUnitTypeBuildItem defineTypeOfImpliedPU( List jdbcDataSourcesBuildItem, //This is from Agroal SPI: safe to use even for Hibernate Reactive Capabilities capabilities) { - // If we have some blocking datasources defined, we can have an implied PU - if (capabilities.isPresent(Capability.HIBERNATE_REACTIVE)) { - // if we don't have any blocking datasources and Hibernate Reactive is present, - // we don't want a blocking persistence unit - return ImpliedBlockingPersistenceUnitTypeBuildItem.none(); - } else { - // even if we don't have any JDBC datasource, we trigger the implied blocking persistence unit - // to properly trigger error conditions and error messages to guide the user - return ImpliedBlockingPersistenceUnitTypeBuildItem.generateImpliedPersistenceUnit(); - } + return ImpliedBlockingPersistenceUnitTypeBuildItem.generateImpliedPersistenceUnit(); } @BuildStep @@ -861,7 +852,8 @@ private void handleHibernateORMWithNoPersistenceXml( || hibernateOrmConfig.defaultPersistenceUnit().isAnyPropertySet(); Map> modelClassesAndPackagesPerPersistencesUnits = getModelClassesAndPackagesPerPersistenceUnits( - hibernateOrmConfig, jpaModel, index.getIndex(), enableDefaultPersistenceUnit); + hibernateOrmConfig, jpaModel, index.getIndex(), enableDefaultPersistenceUnit, + PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME); Set modelClassesAndPackagesForDefaultPersistenceUnit = modelClassesAndPackagesPerPersistencesUnits .getOrDefault(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, Collections.emptySet()); @@ -942,7 +934,8 @@ private static void producePersistenceUnitDescriptorFromConfig( // - the comment at org/hibernate/boot/model/process/internal/ScanningCoordinator.java:246: // "IMPL NOTE : "explicitlyListedClassNames" can contain class or package names..." new ArrayList<>(modelClassesAndPackages), - new Properties()); + new Properties(), + false); MultiTenancyStrategy multiTenancyStrategy = getMultiTenancyStrategy(persistenceUnitConfig.multitenant()); collectDialectConfig(persistenceUnitName, persistenceUnitConfig, @@ -1302,7 +1295,8 @@ private void enhanceEntities(final JpaModelBuildItem jpaModel, } public static Map> getModelClassesAndPackagesPerPersistenceUnits(HibernateOrmConfig hibernateOrmConfig, - JpaModelBuildItem jpaModel, IndexView index, boolean enableDefaultPersistenceUnit) { + JpaModelBuildItem jpaModel, IndexView index, boolean enableDefaultPersistenceUnit, + String defaultPersistenceUnitName) { Map> modelClassesAndPackagesPerPersistenceUnits = new HashMap<>(); boolean hasPackagesInQuarkusConfig = hasPackagesInQuarkusConfig(hibernateOrmConfig); @@ -1327,7 +1321,7 @@ public static Map> getModelClassesAndPackagesPerPersistenceU for (String packageName : hibernateOrmConfig.defaultPersistenceUnit().packages().get()) { packageRules.computeIfAbsent(normalizePackage(packageName), p -> new HashSet<>()) - .add(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME); + .add(defaultPersistenceUnitName); } } @@ -1371,7 +1365,7 @@ public static Map> getModelClassesAndPackagesPerPersistenceU // so we don't need to split them Set allModelClassesAndPackages = new HashSet<>(jpaModel.getAllModelClassNames()); allModelClassesAndPackages.addAll(jpaModel.getAllModelPackageNames()); - return Collections.singletonMap(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, allModelClassesAndPackages); + return Collections.singletonMap(defaultPersistenceUnitName, allModelClassesAndPackages); } Set modelClassesWithPersistenceUnitAnnotations = new TreeSet<>(); diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java index fe343c0f63348a..52a1edd2d3f52e 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java @@ -81,6 +81,10 @@ public boolean isFromPersistenceXml() { return fromPersistenceXml; } + public boolean isReactive() { + return isReactive; + } + public boolean isHibernateValidatorPresent() { return isHibernateValidatorPresent; } @@ -88,8 +92,7 @@ public boolean isHibernateValidatorPresent() { public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition( List integrationStaticDescriptors) { return new QuarkusPersistenceUnitDefinition(descriptor, config, - xmlMappings, isReactive, fromPersistenceXml, isHibernateValidatorPresent, - jsonMapper, xmlMapper, integrationStaticDescriptors); + xmlMappings, isReactive, fromPersistenceXml, isHibernateValidatorPresent, jsonMapper, xmlMapper, integrationStaticDescriptors); } private Optional json(Capabilities capabilities) { diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/ReactivePersistenceUnit.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/ReactivePersistenceUnit.java new file mode 100644 index 00000000000000..e0ea515d848c25 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/ReactivePersistenceUnit.java @@ -0,0 +1,25 @@ +package io.quarkus.hibernate.orm; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; +import jakarta.persistence.EntityManagerFactory; + +/** + * This is used to qualify the {@link EntityManagerFactory} that should be used for Hibernate Reactive. + */ +@Target({ TYPE, FIELD, METHOD, PARAMETER, PACKAGE }) +@Retention(RUNTIME) +@Documented +@Qualifier +public @interface ReactivePersistenceUnit { +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/MultiplePersistenceProviderResolver.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/MultiplePersistenceProviderResolver.java new file mode 100644 index 00000000000000..15ca30f71e2e92 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/MultiplePersistenceProviderResolver.java @@ -0,0 +1,32 @@ +package io.quarkus.hibernate.orm.runtime; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jakarta.persistence.spi.PersistenceProvider; +import jakarta.persistence.spi.PersistenceProviderResolver; + +public final class MultiplePersistenceProviderResolver implements PersistenceProviderResolver { + + private final List persistenceProviders = new ArrayList<>(); + + public MultiplePersistenceProviderResolver(PersistenceProvider... persistenceProviders) { + this.persistenceProviders.addAll(List.of(persistenceProviders)); + } + + @Override + public List getPersistenceProviders() { + return Collections.unmodifiableList(persistenceProviders); + } + + public void addPersistenceProvider(PersistenceProvider persistenceProvider) { + persistenceProviders.add(persistenceProvider); + } + + @Override + public void clearCachedProviders() { + // done! + } + +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceProviderSetup.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceProviderSetup.java index 21bedfc8ab7c2d..b39e25a70c83b9 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceProviderSetup.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceProviderSetup.java @@ -3,6 +3,9 @@ import java.util.List; import java.util.Map; +import jakarta.persistence.spi.PersistenceProviderResolver; +import jakarta.persistence.spi.PersistenceProviderResolverHolder; + import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeDescriptor; public final class PersistenceProviderSetup { @@ -18,8 +21,19 @@ public static void registerStaticInitPersistenceProvider() { public static void registerRuntimePersistenceProvider(HibernateOrmRuntimeConfig hibernateOrmRuntimeConfig, Map> integrationRuntimeDescriptors) { - jakarta.persistence.spi.PersistenceProviderResolverHolder.setPersistenceProviderResolver( - new SingletonPersistenceProviderResolver( - new FastBootHibernatePersistenceProvider(hibernateOrmRuntimeConfig, integrationRuntimeDescriptors))); + + PersistenceProviderResolver persistenceProviderResolver = PersistenceProviderResolverHolder + .getPersistenceProviderResolver(); + if (persistenceProviderResolver == null || + (persistenceProviderResolver != null + && !(persistenceProviderResolver instanceof MultiplePersistenceProviderResolver))) { + persistenceProviderResolver = new MultiplePersistenceProviderResolver(); + PersistenceProviderResolverHolder.setPersistenceProviderResolver(persistenceProviderResolver); + } + + ((MultiplePersistenceProviderResolver) persistenceProviderResolver) + .addPersistenceProvider(new FastBootHibernatePersistenceProvider(hibernateOrmRuntimeConfig, + integrationRuntimeDescriptors)); + } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDescriptor.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDescriptor.java index 22fb6688cdf719..7aae1627326639 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDescriptor.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDescriptor.java @@ -30,11 +30,12 @@ public final class QuarkusPersistenceUnitDescriptor implements PersistenceUnitDe private final SharedCacheMode sharedCacheMode; private final List managedClassNames; private final Properties properties; + private final boolean reactive; public QuarkusPersistenceUnitDescriptor(String name, String configurationName, PersistenceUnitTransactionType transactionType, List managedClassNames, - Properties properties) { + Properties properties, boolean reactive) { this.name = name; this.configurationName = configurationName; this.providerClassName = null; @@ -44,6 +45,7 @@ public QuarkusPersistenceUnitDescriptor(String name, String configurationName, this.sharedCacheMode = null; this.managedClassNames = managedClassNames; this.properties = properties; + this.reactive = reactive; } /** @@ -57,7 +59,7 @@ public QuarkusPersistenceUnitDescriptor(String name, String configurationName, String providerClassName, boolean useQuotedIdentifiers, PersistenceUnitTransactionType transactionType, ValidationMode validationMode, SharedCacheMode sharedCacheMode, List managedClassNames, - Properties properties) { + Properties properties, boolean reactive) { this.name = name; this.configurationName = configurationName; this.providerClassName = providerClassName; @@ -67,6 +69,7 @@ public QuarkusPersistenceUnitDescriptor(String name, String configurationName, this.sharedCacheMode = sharedCacheMode; this.managedClassNames = managedClassNames; this.properties = properties; + this.reactive = reactive; } /** @@ -87,7 +90,7 @@ public static QuarkusPersistenceUnitDescriptor validateAndReadFrom(PersistenceUn return new QuarkusPersistenceUnitDescriptor(toClone.getName(), toClone.getName(), toClone.getProviderClassName(), toClone.isUseQuotedIdentifiers(), toClone.getTransactionType(), toClone.getValidationMode(), toClone.getSharedCacheMode(), - Collections.unmodifiableList(toClone.getManagedClassNames()), toClone.getProperties()); + Collections.unmodifiableList(toClone.getManagedClassNames()), toClone.getProperties(), false); } @Override @@ -179,6 +182,10 @@ public ClassLoader getTempClassLoader() { return null; } + public boolean isReactive() { + return reactive; + } + @Override public void pushClassTransformer(final EnhancementContext enhancementContext) { // has never been supported @@ -208,10 +215,18 @@ private static void verifyIgnoredFields(final PersistenceUnitDescriptor toClone) @Override public String toString() { - return "PersistenceUnitDescriptor{" + "name='" + name + '\'' + ", providerClassName='" + providerClassName - + '\'' + ", useQuotedIdentifiers=" + useQuotedIdentifiers + ", transactionType=" + transactionType - + ", validationMode=" + validationMode + ", sharedCachemode=" + sharedCacheMode + ", managedClassNames=" - + managedClassNames + ", properties=" + properties + '}'; + return "QuarkusPersistenceUnitDescriptor{" + + "name='" + name + '\'' + + ", configurationName='" + configurationName + '\'' + + ", providerClassName='" + providerClassName + '\'' + + ", useQuotedIdentifiers=" + useQuotedIdentifiers + + ", transactionType=" + transactionType + + ", validationMode=" + validationMode + + ", sharedCacheMode=" + sharedCacheMode + + ", managedClassNames=" + managedClassNames + + ", properties=" + properties + + ", isReactive=" + reactive + + '}'; } @Override 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 a26863b5b83736..eaa2cbcf61f69a 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 @@ -3,7 +3,7 @@ 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.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME; +import static io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.REACTIVE_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; @@ -100,15 +100,7 @@ void registerBeans(BuildProducer additionalBeans, Combi ReactiveSessionFactoryProducer.class.getSimpleName()); return; } - if (descriptors.size() == 1) { - // Only register the bean if their EMF dependency is also available, so use the same guard as the ORM extension - additionalBeans.produce(new AdditionalBeanBuildItem(ReactiveSessionFactoryProducer.class)); - } else { - throw new ConfigurationException( - "The Hibernate Reactive extension requires exactly one persistence unit configured: " + descriptors - .stream() - .map(PersistenceUnitDescriptorBuildItem::getPersistenceUnitName).collect(Collectors.toList())); - } + additionalBeans.produce(new AdditionalBeanBuildItem(ReactiveSessionFactoryProducer.class)); } @BuildStep @@ -262,12 +254,13 @@ 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; + String persistenceUnitConfigName = REACTIVE_PERSISTENCE_UNIT_NAME; Map> modelClassesAndPackagesPerPersistencesUnits = HibernateOrmProcessor - .getModelClassesAndPackagesPerPersistenceUnits(hibernateOrmConfig, jpaModel, index.getIndex(), true); + .getModelClassesAndPackagesPerPersistenceUnits(hibernateOrmConfig, jpaModel, index.getIndex(), true, + REACTIVE_PERSISTENCE_UNIT_NAME); Set nonDefaultPUWithModelClassesOrPackages = modelClassesAndPackagesPerPersistencesUnits.entrySet().stream() - .filter(e -> !DEFAULT_PERSISTENCE_UNIT_NAME.equals(e.getKey()) && !e.getValue().isEmpty()) + .filter(e -> !REACTIVE_PERSISTENCE_UNIT_NAME.equals(e.getKey()) && !e.getValue().isEmpty()) .map(Map.Entry::getKey) .collect(Collectors.toSet()); if (!nonDefaultPUWithModelClassesOrPackages.isEmpty()) { @@ -277,7 +270,7 @@ private static QuarkusPersistenceUnitDescriptor generateReactivePersistenceUnit( nonDefaultPUWithModelClassesOrPackages); } Set modelClassesAndPackages = modelClassesAndPackagesPerPersistencesUnits - .getOrDefault(DEFAULT_PERSISTENCE_UNIT_NAME, Collections.emptySet()); + .getOrDefault(REACTIVE_PERSISTENCE_UNIT_NAME, Collections.emptySet()); if (modelClassesAndPackages.isEmpty()) { LOG.warnf("Could not find any entities affected to the Hibernate Reactive persistence unit."); @@ -287,7 +280,8 @@ private static QuarkusPersistenceUnitDescriptor generateReactivePersistenceUnit( HibernateReactive.DEFAULT_REACTIVE_PERSISTENCE_UNIT_NAME, persistenceUnitConfigName, PersistenceUnitTransactionType.RESOURCE_LOCAL, new ArrayList<>(modelClassesAndPackages), - new Properties()); + new Properties(), + true); setDialectAndStorageEngine(dbKindOptional, explicitDialect, explicitDbMinVersion, dbKindDialectBuildItems, persistenceUnitConfig.dialect().storageEngine(), systemProperties, desc); @@ -432,7 +426,7 @@ private static void setDialectAndStorageEngine(Optional dbKind, Optional Optional explicitDbMinVersion, List dbKindDialectBuildItems, Optional storageEngine, BuildProducer systemProperties, QuarkusPersistenceUnitDescriptor desc) { - final String persistenceUnitName = DEFAULT_PERSISTENCE_UNIT_NAME; + final String persistenceUnitName = REACTIVE_PERSISTENCE_UNIT_NAME; Optional dialect = explicitDialect; Optional dbProductName = Optional.empty(); Optional dbProductVersion = explicitDbMinVersion; diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java index 97608331b591bd..6e4b8345a88957 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java @@ -64,6 +64,8 @@ public final class FastBootHibernateReactivePersistenceProvider implements Persi public static final String IMPLEMENTATION_NAME = "org.hibernate.reactive.provider.ReactivePersistenceProvider"; + public static String REACTIVE_PERSISTENCE_UNIT_NAME = ""; + private final ProviderUtil providerUtil = new io.quarkus.hibernate.orm.runtime.ProviderUtil(); private volatile FastBootHibernatePersistenceProvider delegate; @@ -82,10 +84,19 @@ public EntityManagerFactory createEntityManagerFactory(String emName, Map proper if (properties == null) properties = new HashMap(); // These are pre-parsed during image generation: - final List units = PersistenceUnitsHolder.getPersistenceUnitDescriptors(); - for (PersistenceUnitDescriptor unit : units) { - //if the provider is not set, don't use it as people might want to use Hibernate ORM + // We might have multiple persistence unit descriptors of different kinds when using reactive and orm together + // In this case this provider should take care only of reactive units + // Luckily we can return null in this API to use the next provider for the other one + // See jakarta.persistence.Persistence.createEntityManagerFactory(java.lang.String, java.util.Map) + final List units = PersistenceUnitsHolder + .getPersistenceUnitDescriptors() + .stream() + .filter(u -> u.getName().equals(emName)) + .filter(u -> u.isReactive()) // we don't support orm units here + .toList(); + + for (QuarkusPersistenceUnitDescriptor unit : units) { if (IMPLEMENTATION_NAME.equalsIgnoreCase(unit.getProviderClassName()) || unit.getProviderClassName() == null) { EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull(emName, properties); diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactivePersistenceProviderSetup.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactivePersistenceProviderSetup.java index 5f0ebd1d30c970..146de3d2d60862 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactivePersistenceProviderSetup.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactivePersistenceProviderSetup.java @@ -3,8 +3,12 @@ import java.util.List; import java.util.Map; +import jakarta.persistence.spi.PersistenceProviderResolver; +import jakarta.persistence.spi.PersistenceProviderResolverHolder; + +import io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider; import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; -import io.quarkus.hibernate.orm.runtime.SingletonPersistenceProviderResolver; +import io.quarkus.hibernate.orm.runtime.MultiplePersistenceProviderResolver; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeDescriptor; public final class ReactivePersistenceProviderSetup { @@ -20,11 +24,22 @@ public static void registerStaticInitPersistenceProvider() { public static void registerRuntimePersistenceProvider(HibernateOrmRuntimeConfig hibernateOrmRuntimeConfig, Map> integrationRuntimeDescriptors) { - jakarta.persistence.spi.PersistenceProviderResolverHolder - .setPersistenceProviderResolver( - new SingletonPersistenceProviderResolver( - new FastBootHibernateReactivePersistenceProvider(hibernateOrmRuntimeConfig, - integrationRuntimeDescriptors))); + PersistenceProviderResolver persistenceProviderResolver = PersistenceProviderResolverHolder + .getPersistenceProviderResolver(); + if (persistenceProviderResolver == null || + (persistenceProviderResolver != null + && !(persistenceProviderResolver instanceof MultiplePersistenceProviderResolver))) { + persistenceProviderResolver = new MultiplePersistenceProviderResolver(); + PersistenceProviderResolverHolder.setPersistenceProviderResolver(persistenceProviderResolver); + } + + ((MultiplePersistenceProviderResolver) persistenceProviderResolver) + .addPersistenceProvider(new FastBootHibernateReactivePersistenceProvider(hibernateOrmRuntimeConfig, + integrationRuntimeDescriptors)); + ((MultiplePersistenceProviderResolver) persistenceProviderResolver) + .addPersistenceProvider(new FastBootHibernatePersistenceProvider(hibernateOrmRuntimeConfig, + integrationRuntimeDescriptors)); + } } diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java index 1b1a51d01d5865..0eaa6343ff3a40 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java @@ -5,7 +5,6 @@ import jakarta.enterprise.inject.Typed; import jakarta.inject.Inject; import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.PersistenceUnit; import org.hibernate.reactive.common.spi.Implementor; import org.hibernate.reactive.mutiny.Mutiny; @@ -13,12 +12,13 @@ import io.quarkus.arc.DefaultBean; import io.quarkus.arc.Unremovable; +import io.quarkus.hibernate.orm.ReactivePersistenceUnit; import io.quarkus.hibernate.orm.runtime.JPAConfig; public class ReactiveSessionFactoryProducer { @Inject - @PersistenceUnit + @ReactivePersistenceUnit EntityManagerFactory emf; @Inject diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java index 5eb2f7ae1094c9..024a4921220b56 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java @@ -1,5 +1,7 @@ package io.quarkus.hibernate.reactive.runtime.boot; +import static io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.REACTIVE_PERSISTENCE_UNIT_NAME; + import jakarta.persistence.EntityManagerFactory; import org.hibernate.boot.internal.SessionFactoryOptionsBuilder; @@ -7,7 +9,6 @@ import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.reactive.session.impl.ReactiveSessionFactoryImpl; -import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings; import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder; import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; @@ -27,7 +28,7 @@ public EntityManagerFactory build() { try { final SessionFactoryOptionsBuilder optionsBuilder = metadata.buildSessionFactoryOptionsBuilder(); optionsBuilder.enableCollectionInDefaultFetchGroup(true); - populate(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, optionsBuilder, standardServiceRegistry); + populate(REACTIVE_PERSISTENCE_UNIT_NAME, optionsBuilder, standardServiceRegistry); SessionFactoryOptions options = optionsBuilder.buildOptions(); return new ReactiveSessionFactoryImpl(metadata, options, metadata.getBootstrapContext()); } catch (Exception e) { diff --git a/integration-tests/hibernate-reactive-orm-compatibility/pom.xml b/integration-tests/hibernate-reactive-orm-compatibility/pom.xml new file mode 100644 index 00000000000000..e83640b1430be8 --- /dev/null +++ b/integration-tests/hibernate-reactive-orm-compatibility/pom.xml @@ -0,0 +1,292 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-hibernate-reactive-orm-compatibility + Quarkus - Integration Tests - Hibernate Reactive - Compatibility with ORM + Hibernate Reactive related tests to make sure Reactive and ORM can work together + + + vertx-reactive:postgresql://localhost:5431/hibernate_orm_test + + + + + + + io.quarkus + quarkus-hibernate-reactive + + + io.quarkus + quarkus-reactive-pg-client + + + io.quarkus + quarkus-hibernate-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-reactive-pg-client-deployment + ${project.version} + pom + test + + + * + * + + + + + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-postgresql-deployment + ${project.version} + pom + test + + + * + * + + + + + + io.quarkus + quarkus-rest-jsonb + + + io.quarkus + quarkus-elytron-security-properties-file + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + io.quarkus + quarkus-rest-jsonb-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-elytron-security-properties-file-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + + + true + + + + + maven-failsafe-plugin + + true + + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-postgresql + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + + docker-postgresql + + + start-containers + + + + + + io.fabric8 + docker-maven-plugin + + + + ${postgres.image} + postgresql + + + hibernate_orm_test + hibernate_orm_test + hibernate_orm_test + + + 5431:5432 + + + + + (?s)ready to accept connections.*ready to accept connections + + + + + + true + + + + docker-start + compile + + stop + start + + + + docker-stop + post-integration-test + + stop + + + + + + org.codehaus.mojo + exec-maven-plugin + + + docker-prune + generate-resources + + exec + + + ${docker-prune.location} + + + + + + + + + + + diff --git a/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/FriesianCow.java b/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/FriesianCow.java new file mode 100644 index 00000000000000..667a69597c41d1 --- /dev/null +++ b/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/FriesianCow.java @@ -0,0 +1,26 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "Cow") +public class FriesianCow { + + @Id + @GeneratedValue + public Long id; + + public String name; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("FriesianCow{"); + sb.append("id=").append(id); + sb.append(", name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateORMTestEndpoint.java b/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateORMTestEndpoint.java new file mode 100644 index 00000000000000..2a38dc492d79a1 --- /dev/null +++ b/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateORMTestEndpoint.java @@ -0,0 +1,37 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.jboss.logging.Logger; + +import io.quarkus.security.Authenticated; + +@Path("/testsORM") +@Authenticated +public class HibernateORMTestEndpoint { + private static final Logger log = Logger.getLogger(HibernateORMTestEndpoint.class); + + @Inject + SessionFactory sessionFactory; + + @GET + @Path("/blockingCowPersist") + @Transactional(Transactional.TxType.REQUIRES_NEW) + public FriesianCow reactiveCowPersist() { + final FriesianCow cow = new FriesianCow(); + cow.name = "Carolina"; + + log.info("Blocking persist, session factory:" + sessionFactory); + + Session session = (Session) sessionFactory.createEntityManager(); + + session.persist(cow); + return session.createQuery("from FriesianCow f where f.name = :name", FriesianCow.class) + .setParameter("name", cow.name).getSingleResult(); + } +} diff --git a/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpoint.java b/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpoint.java new file mode 100644 index 00000000000000..cce588d16aaefa --- /dev/null +++ b/integration-tests/hibernate-reactive-orm-compatibility/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpoint.java @@ -0,0 +1,42 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.jboss.logging.Logger; + +import io.quarkus.security.Authenticated; +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.pgclient.PgPool; + +@Path("/tests") +@Authenticated +public class HibernateReactiveTestEndpoint { + + private static final Logger log = Logger.getLogger(HibernateReactiveTestEndpoint.class); + + @Inject + Mutiny.SessionFactory sessionFactory; + + // Injecting a Vert.x Pool is not required, it's only used to + // independently validate the contents of the database for the test + @Inject + PgPool pgPool; + + @GET + @Path("/reactiveCowPersist") + public Uni reactiveCowPersist() { + final FriesianCow cow = new FriesianCow(); + cow.name = "Carolina Reactive"; + + log.info("Reactive persist, session factory:" + sessionFactory); + + return sessionFactory + .withTransaction(s -> s.persist(cow)) + .chain(() -> sessionFactory + .withSession(s -> s.createQuery("from FriesianCow f where f.name = :name", FriesianCow.class) + .setParameter("name", cow.name).getSingleResult())); + } +} diff --git a/integration-tests/hibernate-reactive-orm-compatibility/src/main/resources/application.properties b/integration-tests/hibernate-reactive-orm-compatibility/src/main/resources/application.properties new file mode 100644 index 00000000000000..38dda342f68c59 --- /dev/null +++ b/integration-tests/hibernate-reactive-orm-compatibility/src/main/resources/application.properties @@ -0,0 +1,19 @@ +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=hibernate_orm_test +quarkus.datasource.password=hibernate_orm_test + +# Hibernate config +#quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.database.generation=drop-and-create + +# Reactive config +quarkus.datasource.reactive=true +quarkus.datasource.reactive.url=${postgres.reactive.url} + +quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5431/hibernate_orm_test + + +quarkus.security.users.embedded.enabled=true +quarkus.security.users.embedded.users.scott=jb0ss +quarkus.security.users.embedded.plain-text=true +quarkus.security.users.embedded.roles.scott=Admin,admin,Tester,user diff --git a/integration-tests/hibernate-reactive-orm-compatibility/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveCompatibilityORMTest.java b/integration-tests/hibernate-reactive-orm-compatibility/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveCompatibilityORMTest.java new file mode 100644 index 00000000000000..6d6d224deb2d3f --- /dev/null +++ b/integration-tests/hibernate-reactive-orm-compatibility/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveCompatibilityORMTest.java @@ -0,0 +1,32 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import static org.hamcrest.Matchers.containsString; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +/** + * Test various JPA operations running in Quarkus + * + * Also makes sure that these work with a blocking security implementation + */ +@QuarkusTest +public class HibernateReactiveCompatibilityORMTest { + + @Test + public void reactiveCowPersistWithORM() { + RestAssured.given().when() + .auth().preemptive().basic("scott", "jb0ss") + .get("/tests/reactiveCowPersist") + .then() + .body(containsString("\"name\":\"Carolina Reactive\"}")); + + RestAssured.given().when() + .auth().preemptive().basic("scott", "jb0ss") + .get("/testsORM/blockingCowPersist") + .then() + .body(containsString("\"name\":\"Carolina\"}")); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 5d297a61483314..99bab89e7873e1 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -219,6 +219,7 @@ hibernate-reactive-postgresql hibernate-reactive-panache hibernate-reactive-panache-kotlin + hibernate-reactive-orm-compatibility hibernate-search-orm-elasticsearch hibernate-search-orm-elasticsearch-outbox-polling hibernate-search-orm-opensearch