Skip to content

Commit 88c17da

Browse files
committed
Hibernate ORM and Hibernate Reactive cannot be used in the same application fixes #13425
#13425 * Use DataSourcesReactiveBuildTimeConfig to understand if the default data source supports reactive to avoid failing while using reactive and blocking together * ORMReactiveCompatbilityUnitTest - Tests are included for default data source / PU, named Datasource and named PU * Removed ReactivePersistenceUnit annotation, generated the Mutiny.SessionFactory bean in the extension itself * Avoid concurrent access to recorded state while booting FastBootHibernatePersistenceProvider and FastBootHibernateReactivePersistenceProvider in this order (it used to pop the recorded state effectively removing from the other BootProvider) * add quarkus.hibernate-orm.blocking configuration and test * Unused method generateSchema moved to throw an Exception * Avoid running database schema migration twice when using reactive and persisting together * Upgraded documentation
1 parent 9ce6a23 commit 88c17da

File tree

58 files changed

+1314
-244
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1314
-244
lines changed

docs/src/main/asciidoc/hibernate-reactive.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ quarkus.hibernate-orm.database.generation=drop-and-create
112112
Note that these configuration properties are not the same ones as in your typical Hibernate Reactive configuration file.
113113
They will often map to Hibernate Reactive configuration properties but could have different names and don't necessarily map 1:1 to each other.
114114

115+
Blocking (non-reactive) and reactive configuration can be mixed together in the same project. You can take an example from the Quarkus' integration test `quarkus-integration-test-hibernate-reactive-orm-compatibility`
116+
115117
Also, Quarkus will set many Hibernate Reactive configuration settings automatically, and will often use more modern defaults.
116118

117119
WARNING: Configuring Hibernate Reactive using the standard `persistence.xml` configuration file is not supported.
@@ -321,8 +323,6 @@ or even a single named persistence unit.
321323
or xref:hibernate-orm.adoc#schema-approach[schema-based multitenancy] at the moment.
322324
xref:hibernate-orm.adoc#discriminator-approach[Discriminator-based multitenancy], on the other hand, is expected to work correctly.
323325
See https://github.com/quarkusio/quarkus/issues/15959.
324-
* This extension cannot be used at the same time as Hibernate ORM.
325-
See https://github.com/quarkusio/quarkus/issues/13425.
326326
* Integration with the Envers extension is not supported.
327327
* Transaction demarcation cannot be done using `jakarta.transaction.Transactional` or `QuarkusTransaction`;
328328
if you use xref:hibernate-reactive-panache.adoc[Hibernate Reactive with Panache],

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package io.quarkus.hibernate.orm.deployment;
22

3-
import java.util.ArrayList;
4-
import java.util.Arrays;
5-
import java.util.List;
6-
import java.util.Locale;
7-
import java.util.Set;
3+
import java.util.*;
84
import java.util.stream.Collectors;
95

106
import jakarta.enterprise.context.ApplicationScoped;
@@ -214,6 +210,7 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
214210
return;
215211
}
216212

213+
Set<String> createdSessionFactory = new HashSet<>();
217214
for (PersistenceUnitDescriptorBuildItem persistenceUnitDescriptor : persistenceUnitDescriptors) {
218215
String persistenceUnitName = persistenceUnitDescriptor.getPersistenceUnitName();
219216
// Hibernate Reactive does not use the same name for its default persistence unit,
@@ -222,14 +219,30 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
222219
String persistenceUnitConfigName = persistenceUnitDescriptor.getConfigurationName();
223220
boolean isDefaultPU = PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitConfigName);
224221
boolean isNamedPU = !isDefaultPU;
225-
226-
syntheticBeanBuildItemBuildProducer
227-
.produce(createSyntheticBean(persistenceUnitName,
228-
isDefaultPU, isNamedPU,
229-
SessionFactory.class, SESSION_FACTORY_EXPOSED_TYPES, true)
230-
.createWith(recorder.sessionFactorySupplier(persistenceUnitName))
231-
.addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class)))
232-
.done());
222+
boolean isReactive = persistenceUnitDescriptor.isReactive();
223+
224+
ExtendedBeanConfigurator persistenceUnitBean = createSyntheticBean(persistenceUnitName,
225+
isDefaultPU,
226+
isNamedPU,
227+
SessionFactory.class,
228+
SESSION_FACTORY_EXPOSED_TYPES,
229+
true);
230+
231+
// A few cases here:
232+
// - We only have a PersistenceUnitDescriptorBuildItem, and it's blocking, we create the SessionFactory
233+
// - We only have a PersistenceUnitDescriptorBuildItem, and it's reactive, we create the Blocking SessionFactory anyway, as it might be used
234+
// to gather metadata such as the configuration from it see io/quarkus/hibernate/reactive/compatbility/CompatibilityUnitTestBase.testBlockingDisabled
235+
// The Mutiny.SessionFactory API doesn't expose every method the Hibernate SessionFactory does, hence this hack
236+
// - We have multiple PersistenceUnitDescriptorBuildItem, (Reactive + Hibernate scenario), we want to make sure this is created only once
237+
// with the correct info from the blocking PersistenceUnitDescriptorBuildItem
238+
if (!createdSessionFactory.contains(persistenceUnitName)
239+
&& (!isReactive || persistenceUnitDescriptors.size() == 1)) {
240+
syntheticBeanBuildItemBuildProducer
241+
.produce(persistenceUnitBean
242+
.createWith(recorder.sessionFactorySupplier(persistenceUnitName))
243+
.addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class)))
244+
.done());
245+
}
233246

234247
if (capabilities.isPresent(Capability.TRANSACTIONS)
235248
&& capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) {

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ public interface HibernateOrmConfig {
3434
@WithDefault("true")
3535
boolean enabled();
3636

37+
/**
38+
* Whether Hibernate ORM is working in blocking mode.
39+
*
40+
* Think of this as enabling ORM usage while using Hibernate Reactive.
41+
* While using Hibernate ORM and Hibernate Reactive together, blocking is usually disabled when not providing the
42+
* JDBC driver, but if for whatever reason you want to provide the JDBC driver and still block the usage of
43+
* Hibernate ORM, you can use this property.
44+
*
45+
* @asciidoclet
46+
*/
47+
@WithDefault("true")
48+
boolean blocking();
49+
3750
/**
3851
* Database related configuration.
3952
*/

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,11 @@ public void parsePersistenceXmlDescriptors(HibernateOrmConfig config,
286286
public ImpliedBlockingPersistenceUnitTypeBuildItem defineTypeOfImpliedPU(
287287
List<JdbcDataSourceBuildItem> jdbcDataSourcesBuildItem, //This is from Agroal SPI: safe to use even for Hibernate Reactive
288288
Capabilities capabilities) {
289-
// If we have some blocking datasources defined, we can have an implied PU
290-
if (capabilities.isPresent(Capability.HIBERNATE_REACTIVE)) {
289+
if (capabilities.isPresent(Capability.HIBERNATE_REACTIVE) && jdbcDataSourcesBuildItem.isEmpty()) {
291290
// if we don't have any blocking datasources and Hibernate Reactive is present,
292291
// we don't want a blocking persistence unit
293292
return ImpliedBlockingPersistenceUnitTypeBuildItem.none();
294293
} else {
295-
// even if we don't have any JDBC datasource, we trigger the implied blocking persistence unit
296-
// to properly trigger error conditions and error messages to guide the user
297294
return ImpliedBlockingPersistenceUnitTypeBuildItem.generateImpliedPersistenceUnit();
298295
}
299296
}
@@ -351,7 +348,7 @@ public void configurationDescriptorBuilding(
351348
hibernateOrmConfig.database().ormCompatibilityVersion(), Collections.emptyMap()),
352349
null,
353350
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
354-
false, true, isHibernateValidatorPresent(capabilities), jsonMapper, xmlMapper));
351+
true, isHibernateValidatorPresent(capabilities), jsonMapper, xmlMapper));
355352
}
356353

357354
if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) {
@@ -667,7 +664,7 @@ public PersistenceProviderSetUpBuildItem setupPersistenceProvider(HibernateOrmRe
667664
Capabilities capabilities, HibernateOrmRuntimeConfig hibernateOrmRuntimeConfig,
668665
List<HibernateOrmIntegrationRuntimeConfiguredBuildItem> integrationBuildItems,
669666
BuildProducer<RecorderBeanInitializedBuildItem> orderEnforcer) {
670-
if (capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) {
667+
if (capabilities.isPresent(Capability.AGROAL)) {
671668
recorder.setupPersistenceProvider(hibernateOrmRuntimeConfig,
672669
HibernateOrmIntegrationRuntimeConfiguredBuildItem.collectDescriptors(integrationBuildItems));
673670
}
@@ -862,6 +859,12 @@ private void handleHibernateORMWithNoPersistenceXml(
862859
}
863860
}
864861

862+
if (!hibernateOrmConfig.blocking()) {
863+
LOG.infof(
864+
"Hibernate ORM was disabled explicitly by quarkus.hibernate-orm.blocking=false");
865+
return;
866+
}
867+
865868
Optional<JdbcDataSourceBuildItem> defaultJdbcDataSource = jdbcDataSources.stream()
866869
.filter(i -> i.isDefault())
867870
.findFirst();
@@ -952,7 +955,8 @@ private static void producePersistenceUnitDescriptorFromConfig(
952955
// - the comment at org/hibernate/boot/model/process/internal/ScanningCoordinator.java:246:
953956
// "IMPL NOTE : "explicitlyListedClassNames" can contain class or package names..."
954957
new ArrayList<>(modelClassesAndPackages),
955-
new Properties());
958+
new Properties(),
959+
false);
956960

957961
MultiTenancyStrategy multiTenancyStrategy = getMultiTenancyStrategy(persistenceUnitConfig.multitenant());
958962
collectDialectConfig(persistenceUnitName, persistenceUnitConfig,
@@ -1147,7 +1151,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
11471151
persistenceUnitConfig.unsupportedProperties()),
11481152
persistenceUnitConfig.multitenantSchemaDatasource().orElse(null),
11491153
xmlMappings,
1150-
false, false,
1154+
false,
11511155
isHibernateValidatorPresent(capabilities), jsonMapper, xmlMapper));
11521156
}
11531157

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {
2525
private final RecordedConfig config;
2626
private final String multiTenancySchemaDataSource;
2727
private final List<RecordableXmlMapping> xmlMappings;
28-
private final boolean isReactive;
2928
private final boolean fromPersistenceXml;
3029
private final boolean isHibernateValidatorPresent;
3130
private final Optional<FormatMapperKind> jsonMapper;
@@ -35,13 +34,12 @@ public PersistenceUnitDescriptorBuildItem(QuarkusPersistenceUnitDescriptor descr
3534
RecordedConfig config,
3635
String multiTenancySchemaDataSource,
3736
List<RecordableXmlMapping> xmlMappings,
38-
boolean isReactive, boolean fromPersistenceXml,
37+
boolean fromPersistenceXml,
3938
boolean isHibernateValidatorPresent, Optional<FormatMapperKind> jsonMapper, Optional<FormatMapperKind> xmlMapper) {
4039
this.descriptor = descriptor;
4140
this.config = config;
4241
this.multiTenancySchemaDataSource = multiTenancySchemaDataSource;
4342
this.xmlMappings = xmlMappings;
44-
this.isReactive = isReactive;
4543
this.fromPersistenceXml = fromPersistenceXml;
4644
this.isHibernateValidatorPresent = isHibernateValidatorPresent;
4745
this.jsonMapper = jsonMapper;
@@ -80,14 +78,18 @@ public boolean isFromPersistenceXml() {
8078
return fromPersistenceXml;
8179
}
8280

81+
public boolean isReactive() {
82+
return descriptor.isReactive();
83+
}
84+
8385
public boolean isHibernateValidatorPresent() {
8486
return isHibernateValidatorPresent;
8587
}
8688

8789
public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition(
8890
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
8991
return new QuarkusPersistenceUnitDefinition(descriptor, config,
90-
xmlMappings, isReactive, fromPersistenceXml, isHibernateValidatorPresent,
91-
jsonMapper, xmlMapper, integrationStaticDescriptors);
92+
xmlMappings, fromPersistenceXml, isHibernateValidatorPresent, jsonMapper, xmlMapper,
93+
integrationStaticDescriptors);
9294
}
9395
}

extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsCdiEntityManagerTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import jakarta.enterprise.context.ContextNotActiveException;
88
import jakarta.enterprise.context.control.ActivateRequestContext;
99
import jakarta.inject.Inject;
10+
import jakarta.inject.Named;
1011
import jakarta.persistence.EntityManager;
1112
import jakarta.persistence.TransactionRequiredException;
1213
import jakarta.transaction.Transactional;
1314

15+
import org.hibernate.SessionFactory;
1416
import org.junit.jupiter.api.Test;
1517
import org.junit.jupiter.api.extension.RegisterExtension;
1618

@@ -41,6 +43,18 @@ public class MultiplePersistenceUnitsCdiEntityManagerTest {
4143
@PersistenceUnit("inventory")
4244
EntityManager inventoryEntityManager;
4345

46+
// The following sessionFactories are not used in tests but needs to be correctly created, if not the test will fail
47+
@Inject
48+
SessionFactory defaultSessionFactory;
49+
50+
@Inject
51+
@Named("users")
52+
SessionFactory usersFactory;
53+
54+
@Inject
55+
@Named("inventory")
56+
SessionFactory inventoryFactory;
57+
4458
@Test
4559
@Transactional
4660
public void defaultEntityManagerInTransaction() {

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,12 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
165165
continue;
166166
}
167167

168-
RecordedState recordedState = PersistenceUnitsHolder.popRecordedState(persistenceUnitName);
168+
RecordedState recordedState = PersistenceUnitsHolder.getRecordedState(persistenceUnitName);
169169

170170
if (recordedState.isReactive()) {
171-
throw new IllegalStateException(
172-
"Attempting to boot a blocking Hibernate ORM instance on a reactive RecordedState");
171+
log.debug(
172+
"Ignoring reactive RecordedState in FastBootHibernatePersistenceProvider, it'll be handled in FastBootHibernateReactivePersistenceProvider");
173+
continue;
173174
}
174175
final PrevalidatedQuarkusMetadata metadata = recordedState.getMetadata();
175176
var puConfig = hibernateOrmRuntimeConfig.persistenceUnits().get(persistenceUnit.getConfigurationName());
@@ -190,7 +191,8 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
190191
metadata /* Uses the StandardServiceRegistry references by this! */,
191192
standardServiceRegistry /* Mostly ignored! (yet needs to match) */,
192193
runtimeSettings,
193-
validatorFactory, cdiBeanManager, recordedState.getMultiTenancyStrategy());
194+
validatorFactory, cdiBeanManager, recordedState.getMultiTenancyStrategy(),
195+
true);
194196
}
195197

196198
log.debug("Found no matching persistence units");
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.quarkus.hibernate.orm.runtime;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
7+
import jakarta.persistence.spi.PersistenceProvider;
8+
import jakarta.persistence.spi.PersistenceProviderResolver;
9+
10+
public final class MultiplePersistenceProviderResolver implements PersistenceProviderResolver {
11+
12+
private final List<PersistenceProvider> persistenceProviders = new ArrayList<>();
13+
14+
public MultiplePersistenceProviderResolver(PersistenceProvider... persistenceProviders) {
15+
this.persistenceProviders.addAll(List.of(persistenceProviders));
16+
}
17+
18+
@Override
19+
public List<PersistenceProvider> getPersistenceProviders() {
20+
return Collections.unmodifiableList(persistenceProviders);
21+
}
22+
23+
public void addPersistenceProvider(PersistenceProvider persistenceProvider) {
24+
persistenceProviders.add(persistenceProvider);
25+
}
26+
27+
@Override
28+
public void clearCachedProviders() {
29+
// done!
30+
}
31+
32+
}

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceProviderSetup.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import java.util.List;
44
import java.util.Map;
55

6+
import jakarta.persistence.spi.PersistenceProviderResolver;
7+
import jakarta.persistence.spi.PersistenceProviderResolverHolder;
8+
69
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeDescriptor;
710

811
public final class PersistenceProviderSetup {
@@ -18,8 +21,19 @@ public static void registerStaticInitPersistenceProvider() {
1821

1922
public static void registerRuntimePersistenceProvider(HibernateOrmRuntimeConfig hibernateOrmRuntimeConfig,
2023
Map<String, List<HibernateOrmIntegrationRuntimeDescriptor>> integrationRuntimeDescriptors) {
21-
jakarta.persistence.spi.PersistenceProviderResolverHolder.setPersistenceProviderResolver(
22-
new SingletonPersistenceProviderResolver(
23-
new FastBootHibernatePersistenceProvider(hibernateOrmRuntimeConfig, integrationRuntimeDescriptors)));
24+
25+
PersistenceProviderResolver persistenceProviderResolver = PersistenceProviderResolverHolder
26+
.getPersistenceProviderResolver();
27+
if (persistenceProviderResolver == null ||
28+
(persistenceProviderResolver != null
29+
&& !(persistenceProviderResolver instanceof MultiplePersistenceProviderResolver))) {
30+
persistenceProviderResolver = new MultiplePersistenceProviderResolver();
31+
PersistenceProviderResolverHolder.setPersistenceProviderResolver(persistenceProviderResolver);
32+
}
33+
34+
((MultiplePersistenceProviderResolver) persistenceProviderResolver)
35+
.addPersistenceProvider(new FastBootHibernatePersistenceProvider(hibernateOrmRuntimeConfig,
36+
integrationRuntimeDescriptors));
37+
2438
}
2539
}

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitsHolder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ public static List<QuarkusPersistenceUnitDescriptor> getPersistenceUnitDescripto
4848
return persistenceUnits.units;
4949
}
5050

51-
public static RecordedState popRecordedState(String persistenceUnitName) {
51+
public static RecordedState getRecordedState(String persistenceUnitName) {
5252
checkJPAInitialization();
5353
Object key = persistenceUnitName;
5454
if (persistenceUnitName == null) {
5555
key = NO_NAME_TOKEN;
5656
}
57-
return persistenceUnits.recordedStates.remove(key);
57+
return persistenceUnits.recordedStates.get(key);
5858
}
5959

6060
private static List<QuarkusPersistenceUnitDescriptor> convertPersistenceUnits(

0 commit comments

Comments
 (0)