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 2aa61f22adc2fc..254d364920c891 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
@@ -3,10 +3,12 @@
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.service.bytecodeprovider.QuarkusRuntimeBytecodeProviderInitiator.INSTANTIATOR_SUFFIX;
import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_MODE;
import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import java.io.IOException;
import java.net.URL;
@@ -44,6 +46,7 @@
import org.hibernate.boot.archive.scan.spi.ClassDescriptor;
import org.hibernate.boot.archive.scan.spi.PackageDescriptor;
import org.hibernate.boot.beanvalidation.BeanValidationIntegrator;
+import org.hibernate.bytecode.spi.ReflectionOptimizer;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.id.SequenceMismatchStrategy;
import org.hibernate.integrator.spi.Integrator;
@@ -80,6 +83,7 @@
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
+import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -108,6 +112,10 @@
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.deployment.util.IoUtil;
import io.quarkus.deployment.util.ServiceUtil;
+import io.quarkus.gizmo.ClassCreator;
+import io.quarkus.gizmo.MethodCreator;
+import io.quarkus.gizmo.MethodDescriptor;
+import io.quarkus.gizmo.ResultHandle;
import io.quarkus.hibernate.orm.PersistenceUnit;
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy;
import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem;
@@ -612,6 +620,58 @@ public HibernateModelClassCandidatesForFieldAccessBuildItem candidatesForFieldAc
return new HibernateModelClassCandidatesForFieldAccessBuildItem(jpaModel.getManagedClassNames());
}
+ /**
+ * Generate {@link org.hibernate.bytecode.spi.ReflectionOptimizer.InstantiationOptimizer}s for
+ * managed classes to avoid using reflection when Hibernate creates new instances.
+ *
+ * For example, the generated bytecode for an entity {@code com.example.MyEntity} will look something like:
+ *
+ *
+ * {@code
+ * package com.example;
+ *
+ * import org.hibernate.bytecode.spi.ReflectionOptimizer;
+ *
+ * public class MyEntity$QuarkusInstantiator implements ReflectionOptimizer.InstantiationOptimizer {
+ * public Object newInstance() {
+ * return new MyEntity();
+ * }
+ * }
+ * }
+ *
+ */
+ @BuildStep
+ void instantiationOptimizers(JpaModelBuildItem jpaModel, JpaModelIndexBuildItem index,
+ BuildProducer generatedClasses,
+ List additionalJpaModelBuildItems,
+ List persistenceUnitDescriptorBuildItems) {
+ GeneratedClassGizmoAdaptor classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true);
+ // Collect all managed class names to generate optimizers for
+ Set managedClassAndPackageNames = new HashSet<>(jpaModel.getManagedClassNames());
+ for (PersistenceUnitDescriptorBuildItem pud : persistenceUnitDescriptorBuildItems) {
+ managedClassAndPackageNames.addAll(pud.getManagedClassNames());
+ }
+ for (AdditionalJpaModelBuildItem additionalJpaModelBuildItem : additionalJpaModelBuildItems) {
+ managedClassAndPackageNames.add(additionalJpaModelBuildItem.getClassName());
+ }
+ // Generate InstantiationOptimizers for all managed classes
+ for (String i : managedClassAndPackageNames) {
+ ClassInfo classInfo = index.getIndex().getClassByName(i);
+ if (classInfo != null && !classInfo.isAbstract() && classInfo.hasNoArgsConstructor()) {
+ String className = classInfo.name() + INSTANTIATOR_SUFFIX;
+ try (ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput)
+ .interfaces(ReflectionOptimizer.InstantiationOptimizer.class).className(className).build()) {
+ MethodCreator method = classCreator
+ .getMethodCreator("newInstance", Object.class)
+ .setModifiers(ACC_PUBLIC);
+ ResultHandle constructorHandle = method
+ .newInstance(MethodDescriptor.ofConstructor(classInfo.name().toString()));
+ method.returnValue(constructorHandle);
+ }
+ }
+ }
+ }
+
@BuildStep
@Record(RUNTIME_INIT)
public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrmConfig,
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java
index 0b164364d7e419..8bfaf1769f6f46 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java
@@ -9,6 +9,7 @@
import io.quarkus.hibernate.orm.runtime.customized.QuarkusRuntimeProxyFactoryFactory;
public final class QuarkusRuntimeBytecodeProviderInitiator implements StandardServiceInitiator {
+ public static final String INSTANTIATOR_SUFFIX = "$QuarkusInstantiator";
private final QuarkusRuntimeProxyFactoryFactory statefulProxyFactory;
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java
index cde9b0b1093183..939c36cced45f5 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java
@@ -1,5 +1,7 @@
package io.quarkus.hibernate.orm.runtime.service.bytecodeprovider;
+import static io.quarkus.hibernate.orm.runtime.service.bytecodeprovider.QuarkusRuntimeBytecodeProviderInitiator.INSTANTIATOR_SUFFIX;
+
import java.util.Map;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
@@ -35,6 +37,26 @@ public ReflectionOptimizer getReflectionOptimizer(
@Override
public ReflectionOptimizer getReflectionOptimizer(Class> clazz, Map propertyAccessMap) {
+ try {
+ Class> instantiatorClass = Class.forName(clazz.getName() + INSTANTIATOR_SUFFIX, true,
+ Thread.currentThread().getContextClassLoader());
+ ReflectionOptimizer.InstantiationOptimizer optimizer = (ReflectionOptimizer.InstantiationOptimizer) instantiatorClass
+ .getDeclaredConstructor().newInstance();
+ return new ReflectionOptimizer() {
+ @Override
+ public InstantiationOptimizer getInstantiationOptimizer() {
+ return optimizer;
+ }
+
+ @Override
+ public AccessOptimizer getAccessOptimizer() {
+ return null;
+ }
+ };
+ } catch (Exception e) {
+ // Some mapped classes will not have an optimizer, e.g. abstract entities or classes missing a no-args constructor.
+ // If we didn't find the optimizer class, just ignore the exception and return null
+ }
return null;
}