Skip to content

Commit 36ee8d3

Browse files
committed
Prevent startup failing when having mismatched datasource types between Hibernate ORM (blocking)
and Hibernate Reactive by validating datasource configuration at build time. - Blocking persistence units are not created when only reactive datasources are configured - Reactive persistence units are not created when only blocking datasources are configured - Clear error messages are provided when datasources are missing - Created HibernateDataSourceUtil with shared datasource lookup logic - Extracted ReactiveDataSourceBuildItem to new `quarkus-reactive-datasource-spi` module to enable reactive data source checking in the Hibernate ORM extension - Refactored HibernateOrmProcessor and HibernateReactiveProcessor to validate datasource types and don't create the PU when there's no appropriate DS - Fail correctly when using a single extension but the datasource name is mismatched - Fixed ORMReactiveCompatbilityDifferentNamedDataSourceNamedPersistenceUnitBothUnitTest which was incorrectly passing due to dev services - Added new test cases for mixed datasource scenarios Fixes #50634 #47036
1 parent c8efc06 commit 36ee8d3

File tree

16 files changed

+234
-68
lines changed

16 files changed

+234
-68
lines changed

bom/application/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,6 +2332,11 @@
23322332
<artifactId>quarkus-reactive-datasource-deployment</artifactId>
23332333
<version>${project.version}</version>
23342334
</dependency>
2335+
<dependency>
2336+
<groupId>io.quarkus</groupId>
2337+
<artifactId>quarkus-reactive-datasource-spi</artifactId>
2338+
<version>${project.version}</version>
2339+
</dependency>
23352340
<dependency>
23362341
<groupId>io.quarkus</groupId>
23372342
<artifactId>quarkus-reactive-db2-client</artifactId>

extensions/hibernate-orm/deployment/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
<groupId>io.quarkus</groupId>
6262
<artifactId>quarkus-hibernate-validator-spi</artifactId>
6363
</dependency>
64+
<dependency>
65+
<groupId>io.quarkus</groupId>
66+
<artifactId>quarkus-reactive-datasource-spi</artifactId>
67+
</dependency>
6468

6569
<dependency>
6670
<groupId>io.quarkus</groupId>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.quarkus.hibernate.orm.deployment;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import java.util.function.Function;
6+
7+
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
8+
9+
public class HibernateDataSourceUtil {
10+
public static <T> Optional<T> findDataSourceWithNameDefault(String persistenceUnitName,
11+
List<T> datasSources,
12+
Function<T, String> nameExtractor,
13+
Function<T, Boolean> defaultExtractor,
14+
Optional<String> datasource) {
15+
if (datasource.isPresent()) {
16+
String dataSourceName = datasource.get();
17+
return datasSources.stream()
18+
.filter(i -> dataSourceName.equals(nameExtractor.apply(i)))
19+
.findFirst();
20+
} else if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) {
21+
return datasSources.stream()
22+
.filter(i -> defaultExtractor.apply(i))
23+
.findFirst();
24+
} else {
25+
// if it's not the default persistence unit, we mandate an explicit datasource to prevent common errors
26+
return Optional.empty();
27+
}
28+
}
29+
}

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

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.jsonMapperKind;
1010
import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.setDialectAndStorageEngine;
1111
import static io.quarkus.hibernate.orm.deployment.util.HibernateProcessorUtil.xmlMapperKind;
12+
import static io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME;
1213

1314
import java.io.IOException;
1415
import java.net.URL;
@@ -144,6 +145,7 @@
144145
import io.quarkus.hibernate.validator.spi.BeanValidationTraversableResolverBuildItem;
145146
import io.quarkus.panache.hibernate.common.deployment.HibernateEnhancersRegisteredBuildItem;
146147
import io.quarkus.panache.hibernate.common.deployment.HibernateModelClassCandidatesForFieldAccessBuildItem;
148+
import io.quarkus.reactive.datasource.spi.ReactiveDataSourceBuildItem;
147149
import io.quarkus.runtime.LaunchMode;
148150
import io.quarkus.runtime.configuration.ConfigurationException;
149151
import net.bytebuddy.description.type.TypeDescription;
@@ -327,6 +329,7 @@ public void configurationDescriptorBuilding(
327329
ImpliedBlockingPersistenceUnitTypeBuildItem impliedPU,
328330
List<PersistenceXmlDescriptorBuildItem> persistenceXmlDescriptors,
329331
List<JdbcDataSourceBuildItem> jdbcDataSources,
332+
List<ReactiveDataSourceBuildItem> reactiveDataSources,
330333
ApplicationArchivesBuildItem applicationArchivesBuildItem,
331334
LaunchModeBuildItem launchMode,
332335
List<AdditionalJpaModelBuildItem> additionalJpaModelBuildItems,
@@ -380,7 +383,8 @@ public void configurationDescriptorBuilding(
380383

381384
if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) {
382385
handleHibernateORMWithNoPersistenceXml(hibernateOrmConfig, index, persistenceXmlDescriptors,
383-
jdbcDataSources, applicationArchivesBuildItem, launchMode.getLaunchMode(), additionalJpaModelBuildItems,
386+
jdbcDataSources, reactiveDataSources, applicationArchivesBuildItem, launchMode.getLaunchMode(),
387+
additionalJpaModelBuildItems,
384388
jpaModel, capabilities,
385389
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors,
386390
reflectiveMethods, unremovableBeans, dbKindMetadataBuildItems);
@@ -842,6 +846,7 @@ private void handleHibernateORMWithNoPersistenceXml(
842846
CombinedIndexBuildItem index,
843847
List<PersistenceXmlDescriptorBuildItem> descriptors,
844848
List<JdbcDataSourceBuildItem> jdbcDataSources,
849+
List<ReactiveDataSourceBuildItem> reactiveDataSources,
845850
ApplicationArchivesBuildItem applicationArchivesBuildItem,
846851
LaunchMode launchMode,
847852
List<AdditionalJpaModelBuildItem> additionalJpaModelBuildItems,
@@ -907,7 +912,7 @@ private void handleHibernateORMWithNoPersistenceXml(
907912
hibernateOrmConfig.defaultPersistenceUnit(),
908913
modelForDefaultPersistenceUnit.allModelClassAndPackageNames(),
909914
jpaModel.getXmlMappings(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME),
910-
jdbcDataSources, applicationArchivesBuildItem, launchMode, capabilities,
915+
jdbcDataSources, reactiveDataSources, applicationArchivesBuildItem, launchMode, capabilities,
911916
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors,
912917
reflectiveMethods, unremovableBeans, storageEngineCollector, dbKindMetadataBuildItems);
913918
} else if (!modelForDefaultPersistenceUnit.entityClassNames().isEmpty()
@@ -939,7 +944,7 @@ private void handleHibernateORMWithNoPersistenceXml(
939944
hibernateOrmConfig, jpaModel, persistenceUnitName, persistenceUnitEntry.getValue(),
940945
model == null ? Collections.emptySet() : model.allModelClassAndPackageNames(),
941946
jpaModel.getXmlMappings(persistenceUnitName),
942-
jdbcDataSources, applicationArchivesBuildItem, launchMode, capabilities,
947+
jdbcDataSources, reactiveDataSources, applicationArchivesBuildItem, launchMode, capabilities,
943948
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors,
944949
reflectiveMethods, unremovableBeans, storageEngineCollector, dbKindMetadataBuildItems);
945950
}
@@ -957,6 +962,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
957962
Set<String> modelClassesAndPackages,
958963
List<RecordableXmlMapping> xmlMappings,
959964
List<JdbcDataSourceBuildItem> jdbcDataSources,
965+
List<ReactiveDataSourceBuildItem> reactiveDataSources,
960966
ApplicationArchivesBuildItem applicationArchivesBuildItem,
961967
LaunchMode launchMode,
962968
Capabilities capabilities,
@@ -968,8 +974,32 @@ private static void producePersistenceUnitDescriptorFromConfig(
968974
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
969975
Set<String> storageEngineCollector,
970976
List<DatabaseKindDialectBuildItem> dbKindMetadataBuildItems) {
971-
Optional<JdbcDataSourceBuildItem> jdbcDataSource = findJdbcDataSource(persistenceUnitName, persistenceUnitConfig,
972-
jdbcDataSources);
977+
978+
Optional<JdbcDataSourceBuildItem> jdbcDataSource = HibernateDataSourceUtil.findDataSourceWithNameDefault(
979+
persistenceUnitName,
980+
jdbcDataSources,
981+
JdbcDataSourceBuildItem::getName,
982+
JdbcDataSourceBuildItem::isDefault, persistenceUnitConfig.datasource());
983+
984+
Optional<ReactiveDataSourceBuildItem> reactiveDataSource = HibernateDataSourceUtil.findDataSourceWithNameDefault(
985+
persistenceUnitName,
986+
reactiveDataSources,
987+
ReactiveDataSourceBuildItem::getName,
988+
ReactiveDataSourceBuildItem::isDefault, persistenceUnitConfig.datasource());
989+
990+
if (jdbcDataSource.isEmpty() && reactiveDataSource.isPresent()) {
991+
LOG.debugf("The datasource '%s' is only reactive, do not create this PU '%s' as blocking",
992+
persistenceUnitConfig.datasource().orElse(DEFAULT_PERSISTENCE_UNIT_NAME), persistenceUnitName);
993+
return;
994+
}
995+
996+
boolean explicitDataSource = persistenceUnitConfig.datasource().isPresent();
997+
if (jdbcDataSource.isEmpty() && explicitDataSource) {
998+
String dataSourceName = persistenceUnitConfig.datasource().get();
999+
throw PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName,
1000+
DataSourceUtil.dataSourceNotConfigured(dataSourceName));
1001+
}
1002+
9731003
Optional<String> dataSourceName = jdbcDataSource.map(JdbcDataSourceBuildItem::getName);
9741004

9751005
QuarkusPersistenceUnitDescriptor descriptor = new QuarkusPersistenceUnitDescriptor(
@@ -1099,25 +1129,6 @@ private static void collectDialectConfigForPersistenceXml(String persistenceUnit
10991129
}
11001130
}
11011131

1102-
private static Optional<JdbcDataSourceBuildItem> findJdbcDataSource(String persistenceUnitName,
1103-
HibernateOrmConfigPersistenceUnit persistenceUnitConfig, List<JdbcDataSourceBuildItem> jdbcDataSources) {
1104-
if (persistenceUnitConfig.datasource().isPresent()) {
1105-
String dataSourceName = persistenceUnitConfig.datasource().get();
1106-
return Optional.of(jdbcDataSources.stream()
1107-
.filter(i -> dataSourceName.equals(i.getName()))
1108-
.findFirst()
1109-
.orElseThrow(() -> PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName,
1110-
DataSourceUtil.dataSourceNotConfigured(dataSourceName))));
1111-
} else if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) {
1112-
return jdbcDataSources.stream()
1113-
.filter(i -> i.isDefault())
1114-
.findFirst();
1115-
} else {
1116-
// if it's not the default persistence unit, we mandate an explicit datasource to prevent common errors
1117-
return Optional.empty();
1118-
}
1119-
}
1120-
11211132
@SuppressWarnings("deprecation")
11221133
private void enhanceEntities(final JpaModelBuildItem jpaModel,
11231134
BuildProducer<BytecodeTransformerBuildItem> transformers,

extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.Optional;
2020
import java.util.Properties;
2121
import java.util.Set;
22-
import java.util.function.Function;
2322
import java.util.logging.Level;
2423

2524
import jakarta.persistence.PersistenceUnitTransactionType;
@@ -49,6 +48,7 @@
4948
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
5049
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
5150
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
51+
import io.quarkus.hibernate.orm.deployment.HibernateDataSourceUtil;
5252
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
5353
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit;
5454
import io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor;
@@ -189,27 +189,27 @@ private static void producePersistenceUnitFromConfig(HibernateOrmConfig hibernat
189189
BuildProducer<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
190190
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
191191
List<DatabaseKindDialectBuildItem> dbKindDialectBuildItems) {
192-
boolean datasourceNamed = persistenceUnitConfig.datasource().isPresent();
193192

194-
Optional<JdbcDataSourceBuildItem> jdbcDataSource = findDataSourceWithNameDefault(persistenceUnitName,
195-
persistenceUnitConfig,
193+
Optional<JdbcDataSourceBuildItem> jdbcDataSource = HibernateDataSourceUtil.findDataSourceWithNameDefault(
194+
persistenceUnitName,
196195
jdbcDataSources,
197196
JdbcDataSourceBuildItem::getName,
198-
JdbcDataSourceBuildItem::isDefault);
197+
JdbcDataSourceBuildItem::isDefault, persistenceUnitConfig.datasource());
199198

200-
Optional<ReactiveDataSourceBuildItem> reactiveDataSource = findDataSourceWithNameDefault(persistenceUnitName,
201-
persistenceUnitConfig,
199+
Optional<ReactiveDataSourceBuildItem> reactiveDataSource = HibernateDataSourceUtil.findDataSourceWithNameDefault(
200+
persistenceUnitName,
202201
reactiveDataSources,
203202
ReactiveDataSourceBuildItem::getName,
204-
ReactiveDataSourceBuildItem::isDefault);
203+
ReactiveDataSourceBuildItem::isDefault, persistenceUnitConfig.datasource());
205204

206-
if (jdbcDataSource.isPresent() && reactiveDataSource.isEmpty() && datasourceNamed) {
207-
LOG.debugf("The datasource '%s' is blocking, do not create this PU '%s' as reactive",
208-
persistenceUnitConfig.datasource().get(), persistenceUnitName);
205+
boolean explicitDataSource = persistenceUnitConfig.datasource().isPresent();
206+
if (jdbcDataSource.isPresent() && reactiveDataSource.isEmpty()) {
207+
LOG.debugf("The datasource '%s' is only blocking, do not create this PU '%s' as reactive",
208+
persistenceUnitConfig.datasource().orElse(DEFAULT_PERSISTENCE_UNIT_NAME), persistenceUnitName);
209209
return;
210210
}
211211

212-
if (jdbcDataSource.isEmpty() && reactiveDataSource.isEmpty() && datasourceNamed) {
212+
if (reactiveDataSource.isEmpty() && explicitDataSource) {
213213
String dataSourceName = persistenceUnitConfig.datasource().get();
214214
throw PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName,
215215
DataSourceUtil.dataSourceNotConfigured(dataSourceName));
@@ -271,25 +271,6 @@ private static void producePersistenceUnitFromConfig(HibernateOrmConfig hibernat
271271
isHibernateValidatorPresent(capabilities), jsonMapper, xmlMapper));
272272
}
273273

274-
private static <T> Optional<T> findDataSourceWithNameDefault(String persistenceUnitName,
275-
HibernateOrmConfigPersistenceUnit persistenceUnitConfig,
276-
List<T> datasSources,
277-
Function<T, String> nameExtractor, Function<T, Boolean> defaultExtractor) {
278-
if (persistenceUnitConfig.datasource().isPresent()) {
279-
String dataSourceName = persistenceUnitConfig.datasource().get();
280-
return datasSources.stream()
281-
.filter(i -> dataSourceName.equals(nameExtractor.apply(i)))
282-
.findFirst();
283-
} else if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) {
284-
return datasSources.stream()
285-
.filter(i -> defaultExtractor.apply(i))
286-
.findFirst();
287-
} else {
288-
// if it's not the default persistence unit, we mandate an explicit datasource to prevent common errors
289-
return Optional.empty();
290-
}
291-
}
292-
293274
@BuildStep
294275
@Consume(VertxPoolBuildItem.class)
295276
void waitForVertxPool(List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptorBuildItems,

extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/compatibility/ORMReactiveCompatbilityDifferentNamedDataSourceNamedPersistenceUnitBothUnitTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ public class ORMReactiveCompatbilityDifferentNamedDataSourceNamedPersistenceUnit
2727
.setForcedDependencies(List.of(
2828
Dependency.of("io.quarkus", "quarkus-jdbc-postgresql-deployment", Version.getVersion()) // this triggers Agroal
2929
))
30-
.withConfigurationResource("application-unittest-both-named.properties")
30+
.withConfigurationResource("application-unittest-both-named-different.properties")
3131

3232
// Reactive named datasource
33+
.overrideConfigKey("quarkus.datasource.\"named-datasource-reactive\".jdbc", "false")
3334
.overrideConfigKey("quarkus.datasource.\"named-datasource-reactive\".reactive", "true")
3435
.overrideConfigKey("quarkus.datasource.\"named-datasource-reactive\".db-kind", POSTGRES_KIND)
3536
.overrideConfigKey("quarkus.datasource.\"named-datasource-reactive\".username", USERNAME_PWD)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.quarkus.hibernate.reactive.compatibility;
2+
3+
import java.util.List;
4+
5+
import org.hibernate.reactive.mutiny.Mutiny;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.RegisterExtension;
8+
9+
import io.quarkus.builder.Version;
10+
import io.quarkus.hibernate.orm.PersistenceUnit;
11+
import io.quarkus.hibernate.reactive.entities.Hero;
12+
import io.quarkus.maven.dependency.Dependency;
13+
import io.quarkus.test.QuarkusUnitTest;
14+
import io.quarkus.test.vertx.RunOnVertxContext;
15+
import io.quarkus.test.vertx.UniAsserter;
16+
17+
public class ORMReactiveCompatibilityNamedReactiveDefaultBlockingUnitTest extends CompatibilityUnitTestBase {
18+
19+
@RegisterExtension
20+
static final QuarkusUnitTest config = new QuarkusUnitTest()
21+
.withApplicationRoot((jar) -> jar
22+
.addClasses(Hero.class)
23+
.addAsResource("complexMultilineImports.sql", "import.sql"))
24+
.setForcedDependencies(List.of(
25+
Dependency.of("io.quarkus", "quarkus-jdbc-postgresql-deployment", Version.getVersion()) // this triggers Agroal
26+
))
27+
.withConfigurationResource("application-unittest-both-named-reactive-and-default-blocking.properties")
28+
// To have the named PU is reactive we need to explicitly disable the blocking the JDBC data source
29+
.overrideConfigKey("quarkus.datasource.\"named-datasource\".jdbc", "false")
30+
.overrideConfigKey("quarkus.datasource.\"named-datasource\".reactive", "true")
31+
.overrideConfigKey("quarkus.hibernate-orm.\"named-pu\".schema-management.strategy", SCHEMA_MANAGEMENT_STRATEGY)
32+
.overrideConfigKey("quarkus.hibernate-orm.\"named-pu\".datasource", "named-datasource")
33+
.overrideConfigKey("quarkus.hibernate-orm.\"named-pu\".packages", "io.quarkus.hibernate.reactive.entities")
34+
.overrideConfigKey("quarkus.datasource.\"named-datasource\".db-kind", POSTGRES_KIND)
35+
.overrideConfigKey("quarkus.datasource.\"named-datasource\".username", USERNAME_PWD)
36+
.overrideConfigKey("quarkus.datasource.\"named-datasource\".password", USERNAME_PWD)
37+
// Default blocking will need to disable the reactive one, JDBC is enabled by default.
38+
.overrideConfigKey("quarkus.datasource.reactive", "false")
39+
// In such case packages for the default PU should be explicit as well
40+
.overrideConfigKey("quarkus.hibernate-orm.packages", "io.quarkus.hibernate.reactive.entities")
41+
.overrideConfigKey("quarkus.datasource.username", USERNAME_PWD)
42+
.overrideConfigKey("quarkus.datasource.password", USERNAME_PWD)
43+
.overrideConfigKey("quarkus.datasource.db-kind", POSTGRES_KIND)
44+
.overrideConfigKey("quarkus.log.category.\"io.quarkus.hibernate\".level", "DEBUG");
45+
46+
@PersistenceUnit("named-pu")
47+
Mutiny.SessionFactory namedMutinySessionFactory;
48+
49+
@Test
50+
@RunOnVertxContext
51+
public void test(UniAsserter uniAsserter) {
52+
testReactiveWorks(namedMutinySessionFactory, uniAsserter);
53+
}
54+
55+
@Test
56+
public void testBlocking() {
57+
testBlockingWorks();
58+
}
59+
}

0 commit comments

Comments
 (0)