Skip to content

Commit

Permalink
Merge pull request #39005 from mkouba/hibernate-reactive-minor-tweaks
Browse files Browse the repository at this point in the history
Hibernate Reactive: don't make transaction-related beans as unremovable and fail on unconfigured datasource
  • Loading branch information
yrodiere authored Feb 28, 2024
2 parents 93ee818 + 70a11af commit 053cb7c
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
Expand All @@ -11,6 +12,8 @@
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Singleton;
import jakarta.persistence.AttributeConverter;
import jakarta.transaction.TransactionManager;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
Expand All @@ -31,6 +34,7 @@
import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.DotNames;
Expand All @@ -42,12 +46,16 @@
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.hibernate.orm.PersistenceUnit;
import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder;
import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig;
import io.quarkus.hibernate.orm.runtime.JPAConfig;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder;
import io.quarkus.hibernate.orm.runtime.RequestScopedStatelessSessionHolder;
import io.quarkus.hibernate.orm.runtime.TransactionSessions;
import io.quarkus.hibernate.orm.runtime.cdi.QuarkusArcBeanContainer;

@BuildSteps(onlyIf = HibernateOrmEnabled.class)
public class HibernateOrmCdiProcessor {
Expand Down Expand Up @@ -182,7 +190,8 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
.addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class)))
.done());

if (capabilities.isPresent(Capability.TRANSACTIONS)) {
if (capabilities.isPresent(Capability.TRANSACTIONS)
&& capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) {
// Do register a Session/EntityManager bean only if JTA is available
// Note that the Hibernate Reactive extension excludes JTA intentionally
syntheticBeanBuildItemBuildProducer
Expand Down Expand Up @@ -222,7 +231,8 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
.addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class)))
.done());

if (capabilities.isPresent(Capability.TRANSACTIONS)) {
if (capabilities.isPresent(Capability.TRANSACTIONS)
&& capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) {
// Do register a Session/EntityManager bean only if JTA is available
// Note that the Hibernate Reactive extension excludes JTA intentionally
syntheticBeanBuildItemBuildProducer
Expand All @@ -245,6 +255,40 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
}
}

@BuildStep
void registerBeans(HibernateOrmConfig hibernateOrmConfig,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
Capabilities capabilities,
CombinedIndexBuildItem combinedIndex,
List<PersistenceUnitDescriptorBuildItem> descriptors,
JpaModelBuildItem jpaModel) {
if (!HibernateOrmProcessor.hasEntities(jpaModel)) {
return;
}

List<Class<?>> unremovableClasses = new ArrayList<>();
unremovableClasses.add(QuarkusArcBeanContainer.class);

if (capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) {
// The following beans only make sense for Hibernate ORM, not for Hibernate Reactive

if (capabilities.isPresent(Capability.TRANSACTIONS)) {
unremovableClasses.add(TransactionManager.class);
unremovableClasses.add(TransactionSessions.class);
}
unremovableClasses.add(RequestScopedSessionHolder.class);
unremovableClasses.add(RequestScopedStatelessSessionHolder.class);
}
additionalBeans.produce(AdditionalBeanBuildItem.builder().setUnremovable()
.addBeanClasses(unremovableClasses.toArray(new Class<?>[unremovableClasses.size()]))
.build());

// Some user-injectable beans are retrieved programmatically and shouldn't be removed
unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(AttributeConverter.class));
unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(jpaModel.getPotentialCdiBeanClassNames()));
}

@BuildStep
void registerAnnotations(BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<BeanDefiningAnnotationBuildItem> beanDefiningAnnotations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.ValidationMode;
import jakarta.persistence.spi.PersistenceUnitTransactionType;
import jakarta.transaction.TransactionManager;
import jakarta.xml.bind.JAXBElement;

import org.hibernate.boot.archive.scan.spi.ClassDescriptor;
Expand All @@ -66,7 +64,6 @@

import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
import io.quarkus.arc.deployment.RecorderBeanInitializedBuildItem;
Expand Down Expand Up @@ -120,15 +117,11 @@
import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder;
import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder;
import io.quarkus.hibernate.orm.runtime.RequestScopedStatelessSessionHolder;
import io.quarkus.hibernate.orm.runtime.TransactionSessions;
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition;
import io.quarkus.hibernate.orm.runtime.boot.scan.QuarkusScanner;
import io.quarkus.hibernate.orm.runtime.boot.xml.JAXBElementSubstitution;
import io.quarkus.hibernate.orm.runtime.boot.xml.QNameSubstitution;
import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping;
import io.quarkus.hibernate.orm.runtime.cdi.QuarkusArcBeanContainer;
import io.quarkus.hibernate.orm.runtime.config.DialectVersions;
import io.quarkus.hibernate.orm.runtime.dev.HibernateOrmDevIntegrator;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor;
Expand Down Expand Up @@ -592,36 +585,6 @@ void handleNativeImageImportSql(BuildProducer<NativeImageResourceBuildItem> reso
}
}

@BuildStep
void registerBeans(HibernateOrmConfig hibernateOrmConfig,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
Capabilities capabilities,
CombinedIndexBuildItem combinedIndex,
List<PersistenceUnitDescriptorBuildItem> descriptors,
JpaModelBuildItem jpaModel) {
if (!hasEntities(jpaModel)) {
return;
}

List<Class<?>> unremovableClasses = new ArrayList<>();
if (capabilities.isPresent(Capability.TRANSACTIONS)) {
unremovableClasses.add(TransactionManager.class);
unremovableClasses.add(TransactionSessions.class);
}
unremovableClasses.add(RequestScopedSessionHolder.class);
unremovableClasses.add(RequestScopedStatelessSessionHolder.class);
unremovableClasses.add(QuarkusArcBeanContainer.class);

additionalBeans.produce(AdditionalBeanBuildItem.builder().setUnremovable()
.addBeanClasses(unremovableClasses.toArray(new Class<?>[unremovableClasses.size()]))
.build());

// Some user-injectable beans are retrieved programmatically and shouldn't be removed
unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(AttributeConverter.class));
unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(jpaModel.getPotentialCdiBeanClassNames()));
}

@Consume(InterceptedStaticMethodsTransformersRegisteredBuildItem.class)
@BuildStep
@SuppressWarnings("deprecation")
Expand Down Expand Up @@ -801,7 +764,7 @@ private static List<String> getSqlLoadScript(Optional<List<String>> sqlLoadScrip
}
}

private boolean hasEntities(JpaModelBuildItem jpaModel) {
static boolean hasEntities(JpaModelBuildItem jpaModel) {
return !jpaModel.getEntityClassNames().isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,19 @@ public final class HibernateReactiveProcessor {
void registerBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans, CombinedIndexBuildItem combinedIndex,
List<PersistenceUnitDescriptorBuildItem> descriptors,
JpaModelBuildItem jpaModel) {
if (!hasEntities(jpaModel)) {
LOG.warnf("Skip registration of the %s bean - no JPA entities were found",
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 {
LOG.warnf(
"Skipping registration of %s bean because exactly one persistence unit is required for their registration",
ReactiveSessionFactoryProducer.class.getSimpleName());
throw new ConfigurationException(
"The Hibernate Reactive extension requires exactly one persistence unit configured: " + descriptors
.stream()
.map(PersistenceUnitDescriptorBuildItem::getPersistenceUnitName).collect(Collectors.toList()));
}
}

Expand Down Expand Up @@ -172,7 +178,12 @@ public void buildReactivePersistenceUnit(
.orElse(!dataSourcesBuildTimeConfig.hasNamedDataSources()),
curateOutcomeBuildItem);

if (dbKindOptional.isPresent()) {
if (dbKindOptional.isEmpty()) {
throw new ConfigurationException(
"The default datasource must be configured for Hibernate Reactive. Refer to https://quarkus.io/guides/datasource for guidance.",
Set.of("quarkus.datasource.db-kind", "quarkus.datasource.username",
"quarkus.datasource.password"));
} else {
HibernateOrmConfigPersistenceUnit persistenceUnitConfig = hibernateOrmConfig.defaultPersistenceUnit();
ParsedPersistenceXmlDescriptor reactivePU = generateReactivePersistenceUnit(
hibernateOrmConfig, index, persistenceUnitConfig, jpaModel,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.quarkus.hibernate.reactive.config.datasource;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.reactive.config.MyEntity;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.test.QuarkusUnitTest;

public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest {
Expand All @@ -13,7 +16,11 @@ public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest {
.withApplicationRoot((jar) -> jar
.addClass(MyEntity.class))
.overrideConfigKey("quarkus.hibernate-orm.datasource", "ds-1")
.overrideConfigKey("quarkus.hibernate-orm.database.generation", "drop-and-create");
.overrideConfigKey("quarkus.hibernate-orm.database.generation", "drop-and-create")
.assertException(t -> assertThat(t)
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining(
"The default datasource must be configured for Hibernate Reactive. Refer to https://quarkus.io/guides/datasource for guidance."));

@Test
public void testInvalidConfiguration() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.quarkus.hibernate.reactive.config.datasource;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.reactive.config.MyEntity;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.test.QuarkusUnitTest;

public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest {
Expand All @@ -13,7 +16,11 @@ public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest {
.withApplicationRoot((jar) -> jar
.addClass(MyEntity.class))
// The datasource won't be truly "unconfigured" if dev services are enabled
.overrideConfigKey("quarkus.devservices.enabled", "false");
.overrideConfigKey("quarkus.devservices.enabled", "false")
.assertException(t -> assertThat(t)
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining(
"The default datasource must be configured for Hibernate Reactive. Refer to https://quarkus.io/guides/datasource for guidance."));

@Test
public void testInvalidConfiguration() {
Expand Down

0 comments on commit 053cb7c

Please sign in to comment.