Skip to content

Commit

Permalink
Hibernate ORM - make use of synthetic injection points
Browse files Browse the repository at this point in the history
- register Session/EntityManager bean only if JTA is present
  • Loading branch information
mkouba committed Feb 17, 2023
1 parent 61a4043 commit 81168bc
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import jakarta.enterprise.context.ApplicationScoped;
Expand All @@ -14,6 +13,7 @@
import org.hibernate.SessionFactory;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.Type;
Expand All @@ -23,18 +23,23 @@
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.Transformation;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.hibernate.orm.PersistenceUnit;
import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder;
import io.quarkus.hibernate.orm.runtime.JPAConfig;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.orm.runtime.TransactionSessions;

@BuildSteps(onlyIf = HibernateOrmEnabled.class)
public class HibernateOrmCdiProcessor {
Expand Down Expand Up @@ -113,6 +118,7 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
ImpliedBlockingPersistenceUnitTypeBuildItem impliedBlockingPersistenceUnitType,
List<JdbcDataSourceBuildItem> jdbcDataSources, // just make sure the datasources are initialized
Capabilities capabilities,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer) {
if (persistenceUnitDescriptors.isEmpty()) {
Expand All @@ -127,17 +133,22 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
true, true,
SessionFactory.class, SESSION_FACTORY_EXPOSED_TYPES,
recorder.sessionFactorySupplier(persistenceUnitName),
true));

syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
true, true,
Session.class, SESSION_EXPOSED_TYPES,
recorder.sessionSupplier(persistenceUnitName),
false));

SessionFactory.class, SESSION_FACTORY_EXPOSED_TYPES, true)
.createWith(recorder.sessionFactorySupplier(persistenceUnitName))
.addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class)))
.done());

if (capabilities.isPresent(Capability.TRANSACTIONS)) {
// Do register a Session/EntityManager bean only if JTA is available
// Note that the Hibernate Reactive extension excludes JTA intentionally
syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
true, true,
Session.class, SESSION_EXPOSED_TYPES, false)
.createWith(recorder.sessionSupplier(persistenceUnitName))
.addInjectionPoint(ClassType.create(DotName.createSimple(TransactionSessions.class)))
.done());
}
return;
}

Expand All @@ -153,16 +164,22 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder,
syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
isDefaultPU, isNamedPU,
SessionFactory.class, SESSION_FACTORY_EXPOSED_TYPES,
recorder.sessionFactorySupplier(persistenceUnitName),
true));

syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
isDefaultPU, isNamedPU,
Session.class, SESSION_EXPOSED_TYPES,
recorder.sessionSupplier(persistenceUnitName),
false));
SessionFactory.class, SESSION_FACTORY_EXPOSED_TYPES, true)
.createWith(recorder.sessionFactorySupplier(persistenceUnitName))
.addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class)))
.done());

if (capabilities.isPresent(Capability.TRANSACTIONS)) {
// Do register a Session/EntityManager bean only if JTA is available
// Note that the Hibernate Reactive extension excludes JTA intentionally
syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
isDefaultPU, isNamedPU,
Session.class, SESSION_EXPOSED_TYPES, false)
.createWith(recorder.sessionSupplier(persistenceUnitName))
.addInjectionPoint(ClassType.create(DotName.createSimple(TransactionSessions.class)))
.done());
}
}
}

Expand Down Expand Up @@ -200,17 +217,16 @@ void validatePersistenceUnitExtensions(ValidationPhaseBuildItem validationPhase,
}
}

private static <T> SyntheticBeanBuildItem createSyntheticBean(String persistenceUnitName,
private static <T> ExtendedBeanConfigurator createSyntheticBean(String persistenceUnitName,
boolean isDefaultPersistenceUnit, boolean isNamedPersistenceUnit,
Class<T> type, List<DotName> allExposedTypes, Supplier<T> supplier, boolean defaultBean) {
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
Class<T> type, List<DotName> allExposedTypes, boolean defaultBean) {
ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(type)
// NOTE: this is using ApplicationScope and not Singleton, by design, in order to be mockable
// See https://github.com/quarkusio/quarkus/issues/16437
.scope(ApplicationScoped.class)
.unremovable()
.setRuntimeInit()
.supplier(supplier);
.setRuntimeInit();

for (DotName exposedType : allExposedTypes) {
configurator.addType(exposedType);
Expand All @@ -228,6 +244,6 @@ private static <T> SyntheticBeanBuildItem createSyntheticBean(String persistence
configurator.addQualifier().annotation(PersistenceUnit.class).addValue("value", persistenceUnitName).done();
}

return configurator.done();
return configurator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public void entityManager() {
assertThat(entityManager).isNotNull();
// However, any attempt to use it at runtime will fail.
assertThatThrownBy(() -> entityManager.find(MyEntity.class, 0L))
// Note that unlike for EntityManagerFactory/SessionFactory we get an IllegalStateException because
// the real Session/EntityManager instance is created lazily through SessionLazyDelegator in HibernateOrmRecorder.sessionSupplier()
.isInstanceOf(IllegalStateException.class)
.hasMessageContainingAll(
"Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit <default>",
Expand All @@ -87,6 +89,8 @@ public void session() {
assertThat(session).isNotNull();
// However, any attempt to use it at runtime will fail.
assertThatThrownBy(() -> session.find(MyEntity.class, 0L))
// Note that unlike for EntityManagerFactory/SessionFactory we get an IllegalStateException because
// the real Session/EntityManager instance is created lazily through SessionLazyDelegator in HibernateOrmRecorder.sessionSupplier()
.isInstanceOf(IllegalStateException.class)
.hasMessageContainingAll(
"Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit <default>",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import jakarta.inject.Inject;
Expand All @@ -18,7 +19,7 @@
import org.hibernate.integrator.spi.Integrator;
import org.jboss.logging.Logger;

import io.quarkus.arc.Arc;
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.arc.runtime.BeanContainerListener;
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition;
Expand Down Expand Up @@ -95,11 +96,12 @@ public void startAllPersistenceUnits(BeanContainer beanContainer) {
beanContainer.beanInstance(JPAConfig.class).startAll();
}

public Supplier<SessionFactory> sessionFactorySupplier(String persistenceUnitName) {
return new Supplier<SessionFactory>() {
public Function<SyntheticCreationalContext<SessionFactory>, SessionFactory> sessionFactorySupplier(
String persistenceUnitName) {
return new Function<SyntheticCreationalContext<SessionFactory>, SessionFactory>() {
@Override
public SessionFactory get() {
SessionFactory sessionFactory = Arc.container().instance(JPAConfig.class).get()
public SessionFactory apply(SyntheticCreationalContext<SessionFactory> context) {
SessionFactory sessionFactory = context.getInjectedReference(JPAConfig.class)
.getEntityManagerFactory(persistenceUnitName)
.unwrap(SessionFactory.class);

Expand All @@ -108,12 +110,12 @@ public SessionFactory get() {
};
}

public Supplier<Session> sessionSupplier(String persistenceUnitName) {
return new Supplier<Session>() {
public Function<SyntheticCreationalContext<Session>, Session> sessionSupplier(String persistenceUnitName) {
return new Function<SyntheticCreationalContext<Session>, Session>() {

@Override
public Session get() {
TransactionSessions transactionSessions = Arc.container()
.instance(TransactionSessions.class).get();
public Session apply(SyntheticCreationalContext<Session> context) {
TransactionSessions transactionSessions = context.getInjectedReference(TransactionSessions.class);
return new SessionLazyDelegator(new Supplier<Session>() {
@Override
public Session get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertThrows;

import jakarta.enterprise.inject.CreationException;
import jakarta.persistence.EntityManagerFactory;

import org.hibernate.SessionFactory;
Expand Down Expand Up @@ -30,7 +32,8 @@ public void entityManagerFactory() {
// So the bean cannot be null.
assertThat(entityManagerFactory).isNotNull();
// However, any attempt to use it at runtime will fail.
assertThatThrownBy(entityManagerFactory::getMetamodel)
CreationException e = assertThrows(CreationException.class, () -> entityManagerFactory.getMetamodel());
assertThat(e.getCause())
.isInstanceOf(IllegalStateException.class)
.hasMessageContainingAll(
"Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit default-reactive",
Expand All @@ -46,7 +49,8 @@ public void sessionFactory() {
// So the bean cannot be null.
assertThat(sessionFactory).isNotNull();
// However, any attempt to use it at runtime will fail.
assertThatThrownBy(sessionFactory::getMetamodel)
CreationException e = assertThrows(CreationException.class, () -> sessionFactory.getMetamodel());
assertThat(e.getCause())
.isInstanceOf(IllegalStateException.class)
.hasMessageContainingAll(
"Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit default-reactive",
Expand Down

0 comments on commit 81168bc

Please sign in to comment.