Skip to content

Commit

Permalink
Configure Hibernate with a custom list of StandardServiceInitiator
Browse files Browse the repository at this point in the history
This commit updates SpringHibernateJpaPersistenceProvider to
leverage a custom list of StandardServiceInitiator in order to
remove ByteBuddy runtime reachability during the native image
compilation.

Such customization could be used for further Hibernate
optimizations (native related or not).
  • Loading branch information
sdeleuze committed Aug 14, 2023
1 parent 443e3d5 commit f367f7a
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@
package org.springframework.orm.jpa.vendor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.spi.PersistenceUnitInfo;
import org.hibernate.boot.cfgxml.internal.ConfigLoader;
import org.hibernate.boot.cfgxml.spi.LoadedConfig;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;

import org.springframework.core.NativeDetector;
import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
Expand All @@ -43,24 +48,16 @@
* @since 4.1
* @see Configuration#addPackage
*/
@SuppressWarnings("removal") // for Environment properties on Hibernate 6.2
class SpringHibernateJpaPersistenceProvider extends HibernatePersistenceProvider {

static {
if (NativeDetector.inNativeImage()) {
System.setProperty(Environment.BYTECODE_PROVIDER, Environment.BYTECODE_PROVIDER_NAME_NONE);
System.setProperty(Environment.USE_REFLECTION_OPTIMIZER, Boolean.FALSE.toString());
}
}

@Override
@SuppressWarnings({"rawtypes", "unchecked"}) // on Hibernate 6
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
final List<String> mergedClassesAndPackages = new ArrayList<>(info.getManagedClassNames());
if (info instanceof SmartPersistenceUnitInfo smartInfo) {
mergedClassesAndPackages.addAll(smartInfo.getManagedPackages());
}
return new EntityManagerFactoryBuilderImpl(
return new SpringEntityManagerFactoryBuilder(
new PersistenceUnitInfoDescriptor(info) {
@Override
public List<String> getManagedClassNames() {
Expand All @@ -75,4 +72,34 @@ public void pushClassTransformer(EnhancementContext enhancementContext) {
}, properties).build();
}

static class SpringEntityManagerFactoryBuilder extends EntityManagerFactoryBuilderImpl {

@SuppressWarnings("rawtypes")
public SpringEntityManagerFactoryBuilder(PersistenceUnitDescriptor persistenceUnitDescriptor, Map properties) {
super(persistenceUnitDescriptor, properties);
}

@Override
protected StandardServiceRegistryBuilder getStandardServiceRegistryBuilder(BootstrapServiceRegistry bsr) {
return new SpringServiceRegistryBuilder(bsr);
}
}

static class SpringServiceRegistryBuilder extends StandardServiceRegistryBuilder {

@SuppressWarnings("rawtypes")
public SpringServiceRegistryBuilder(BootstrapServiceRegistry bootstrapServiceRegistry) {
super(bootstrapServiceRegistry,
new HashMap(),
new ConfigLoader(bootstrapServiceRegistry),
new LoadedConfig(null) {
@Override
protected void addConfigurationValues(Map configurationValues) {
// here, do nothing
}
},
SpringServiceInitiators.buildInitiatorList());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.orm.jpa.vendor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.hibernate.boot.cfgxml.internal.CfgXmlAccessServiceInitiator;
import org.hibernate.boot.internal.DefaultSessionFactoryBuilderInitiator;
import org.hibernate.boot.registry.StandardServiceInitiator;
import org.hibernate.bytecode.internal.BytecodeProviderInitiator;
import org.hibernate.bytecode.internal.ProxyFactoryFactoryInitiator;
import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.cache.internal.RegionFactoryInitiator;
import org.hibernate.engine.config.internal.ConfigurationServiceInitiator;
import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator;
import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator;
import org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator;
import org.hibernate.engine.jdbc.cursor.internal.RefCursorSupportInitiator;
import org.hibernate.engine.jdbc.dialect.internal.DialectFactoryInitiator;
import org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
import org.hibernate.engine.jndi.internal.JndiServiceInitiator;
import org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator;
import org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator;
import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator;
import org.hibernate.persister.internal.PersisterClassResolverInitiator;
import org.hibernate.persister.internal.PersisterFactoryInitiator;
import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator;
import org.hibernate.resource.beans.spi.ManagedBeanRegistryInitiator;
import org.hibernate.resource.transaction.internal.TransactionCoordinatorBuilderInitiator;
import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.tool.schema.internal.SchemaManagementToolInitiator;

import org.springframework.core.NativeDetector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
* Alternative to {@link org.hibernate.service.StandardServiceInitiators} which avoids
* making ByteBuddy reachable on native images. Can also be used for other Hibernate
* infrastructure optimizations. Supports both Hibernate 5.6.x and Hibernate 6.x.
*
* @author Sebastien Deleuze
* @since 6.1
*/
abstract class SpringServiceInitiators {

static final String SSI_FIELD_NAME = "INSTANCE";

// Hibernate 5.6.x
static final String JSI_CLASS_NAME = "org.hibernate.jmx.internal.JmxServiceInitiator";
static final String QTFI_CLASS_NAME = "org.hibernate.hql.internal.QueryTranslatorFactoryInitiator";
static final String MIGFI_CLASS_NAME = "org.hibernate.id.factory.internal.MutableIdentifierGeneratorFactoryInitiator";

// Hibernate 6.x
static final String SIGFI_CLASS_NAME = "org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactoryInitiator";
static final String SSEI_CLASS_NAME = "org.hibernate.tool.schema.internal.script.SqlScriptExtractorInitiator";
static final String ISCEI_CLASS_NAME = "org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractorInitiator";
static final String MESI_CLASS_NAME = "org.hibernate.engine.jdbc.mutation.internal.MutationExecutorServiceInitiator";
static final String SSLI_CLASS_NAME = "org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator";
static final String JVMPPI_CLASS_NAME = "org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerProviderInitiator";
static final String SMTMSPI_CLASS_NAME = "org.hibernate.query.sqm.mutation.internal.SqmMultiTableMutationStrategyProviderInitiator";
static final String PMSI_CLASS_NAME = "org.hibernate.sql.ast.internal.ParameterMarkerStrategyInitiator";
static final String BLFI_CLASS_NAME = "org.hibernate.loader.ast.internal.BatchLoaderFactoryInitiator";

@Nullable
private static final StandardServiceInitiator<?> standardIdentifierGeneratorFactoryInitiator;

@Nullable
private static final StandardServiceInitiator<?> sqlScriptExtractorInitiator;

@Nullable
private static final StandardServiceInitiator<?> importSqlCommandExtractorInitiator;

@Nullable
private static final StandardServiceInitiator<?> jmxServiceInitiator;

@Nullable
private static final StandardServiceInitiator<?> mutationExecutorServiceInitiator;

@Nullable
private static final StandardServiceInitiator<?> sqlStatementLoggerInitiator;

@Nullable
private static final StandardServiceInitiator<?> queryTranslatorFactoryInitiator;

@Nullable
private static final StandardServiceInitiator<?> mutableIdentifierGeneratorFactoryInitiator;

@Nullable
private static final StandardServiceInitiator<?> jdbcValuesMappingProducerProviderInitiator;

@Nullable
private static final StandardServiceInitiator<?> sqmMultiTableMutationStrategyProviderInitiator;

@Nullable
private static final StandardServiceInitiator<?> parameterMarkerStrategyInitiator;

@Nullable
private static final StandardServiceInitiator<?> batchLoaderFactoryInitiator;

static {
standardIdentifierGeneratorFactoryInitiator = instantiateServiceInitiator(SIGFI_CLASS_NAME);
sqlScriptExtractorInitiator = instantiateServiceInitiator(SSEI_CLASS_NAME);
importSqlCommandExtractorInitiator = instantiateServiceInitiator(ISCEI_CLASS_NAME);
jmxServiceInitiator = instantiateServiceInitiator(JSI_CLASS_NAME);
mutationExecutorServiceInitiator = instantiateServiceInitiator(MESI_CLASS_NAME);
sqlStatementLoggerInitiator = instantiateServiceInitiator(SSLI_CLASS_NAME);
queryTranslatorFactoryInitiator = instantiateServiceInitiator(QTFI_CLASS_NAME);
mutableIdentifierGeneratorFactoryInitiator = instantiateServiceInitiator(MIGFI_CLASS_NAME);
jdbcValuesMappingProducerProviderInitiator = instantiateServiceInitiator(JVMPPI_CLASS_NAME);
sqmMultiTableMutationStrategyProviderInitiator = instantiateServiceInitiator(SMTMSPI_CLASS_NAME);
parameterMarkerStrategyInitiator = instantiateServiceInitiator(PMSI_CLASS_NAME);
batchLoaderFactoryInitiator = instantiateServiceInitiator(BLFI_CLASS_NAME);
}

@SuppressWarnings("rawtypes")
public static List<StandardServiceInitiator> buildInitiatorList() {
final ArrayList<StandardServiceInitiator<?>> serviceInitiators = new ArrayList<>();
serviceInitiators.add(DefaultSessionFactoryBuilderInitiator.INSTANCE);
if (standardIdentifierGeneratorFactoryInitiator != null) {
serviceInitiators.add(standardIdentifierGeneratorFactoryInitiator);
}
if (NativeDetector.inNativeImage()) {
serviceInitiators.add(new NoneBytecodeProviderInitiator());
}
else {
serviceInitiators.add(BytecodeProviderInitiator.INSTANCE);
}
serviceInitiators.add(ProxyFactoryFactoryInitiator.INSTANCE);
serviceInitiators.add(CfgXmlAccessServiceInitiator.INSTANCE);
serviceInitiators.add(ConfigurationServiceInitiator.INSTANCE);
serviceInitiators.add(PropertyAccessStrategyResolverInitiator.INSTANCE);
if (sqlScriptExtractorInitiator != null) {
serviceInitiators.add(sqlScriptExtractorInitiator);
}
else {
Assert.notNull(importSqlCommandExtractorInitiator,
"Either org.hibernate.tool.schema.internal.script.SqlScriptExtractorInitiator or " +
"org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractorInitiator are expected to be available " +
"in the classpath, none has been found.");
serviceInitiators.add(importSqlCommandExtractorInitiator);
}
serviceInitiators.add(SchemaManagementToolInitiator.INSTANCE);
serviceInitiators.add(JdbcEnvironmentInitiator.INSTANCE);
serviceInitiators.add(JndiServiceInitiator.INSTANCE);
if (jmxServiceInitiator != null) {
serviceInitiators.add(jmxServiceInitiator);
}
serviceInitiators.add(PersisterClassResolverInitiator.INSTANCE);
serviceInitiators.add(PersisterFactoryInitiator.INSTANCE);
serviceInitiators.add(ConnectionProviderInitiator.INSTANCE);
serviceInitiators.add(MultiTenantConnectionProviderInitiator.INSTANCE);
serviceInitiators.add(DialectResolverInitiator.INSTANCE);
serviceInitiators.add(DialectFactoryInitiator.INSTANCE);
if (mutationExecutorServiceInitiator != null) {
serviceInitiators.add(mutationExecutorServiceInitiator);
}
serviceInitiators.add(BatchBuilderInitiator.INSTANCE);
if (sqlStatementLoggerInitiator != null) {
serviceInitiators.add(sqlStatementLoggerInitiator);
}
serviceInitiators.add(JdbcServicesInitiator.INSTANCE);
serviceInitiators.add(RefCursorSupportInitiator.INSTANCE);
if (queryTranslatorFactoryInitiator != null) {
serviceInitiators.add(queryTranslatorFactoryInitiator);
}
if (mutableIdentifierGeneratorFactoryInitiator != null) {
serviceInitiators.add(mutableIdentifierGeneratorFactoryInitiator);
}
serviceInitiators.add(JtaPlatformResolverInitiator.INSTANCE);
serviceInitiators.add(JtaPlatformInitiator.INSTANCE);
serviceInitiators.add(SessionFactoryServiceRegistryFactoryInitiator.INSTANCE);
serviceInitiators.add(RegionFactoryInitiator.INSTANCE);
serviceInitiators.add(TransactionCoordinatorBuilderInitiator.INSTANCE);
serviceInitiators.add(ManagedBeanRegistryInitiator.INSTANCE);
serviceInitiators.add(EntityCopyObserverFactoryInitiator.INSTANCE);
if (jdbcValuesMappingProducerProviderInitiator != null) {
serviceInitiators.add(jdbcValuesMappingProducerProviderInitiator);
}
if (sqmMultiTableMutationStrategyProviderInitiator != null) {
serviceInitiators.add(sqmMultiTableMutationStrategyProviderInitiator);
}
if (parameterMarkerStrategyInitiator != null) {
serviceInitiators.add(parameterMarkerStrategyInitiator);
}
if (batchLoaderFactoryInitiator != null) {
serviceInitiators.add(batchLoaderFactoryInitiator);
}
serviceInitiators.trimToSize();
return Collections.unmodifiableList(serviceInitiators);
}

@Nullable
private static StandardServiceInitiator<?> instantiateServiceInitiator(String className) {
ClassLoader classLoader = SpringServiceInitiators.class.getClassLoader();
try {
Class<?> clazz = ClassUtils.forName(className, classLoader);
return (StandardServiceInitiator<?>) clazz.getField(SSI_FIELD_NAME).get(null);
}
catch (Throwable ex) {
return null;
}
}

private static class NoneBytecodeProviderInitiator implements StandardServiceInitiator<BytecodeProvider> {

@Override
@SuppressWarnings("rawtypes")
public BytecodeProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
return new org.hibernate.bytecode.internal.none.BytecodeProviderImpl();
}

@Override
public Class<BytecodeProvider> getServiceInitiated() {
return BytecodeProvider.class;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.orm.jpa.vendor;

import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.ClassUtils;

public class SpringServiceInitiatorsRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ReflectionHints reflectionHints = hints.reflection();
for (String className : new String[] {
SpringServiceInitiators.JSI_CLASS_NAME,
SpringServiceInitiators.QTFI_CLASS_NAME,
SpringServiceInitiators.MIGFI_CLASS_NAME,
SpringServiceInitiators.SIGFI_CLASS_NAME,
SpringServiceInitiators.SSEI_CLASS_NAME,
SpringServiceInitiators.ISCEI_CLASS_NAME,
SpringServiceInitiators.MESI_CLASS_NAME,
SpringServiceInitiators.SSLI_CLASS_NAME,
SpringServiceInitiators.JVMPPI_CLASS_NAME,
SpringServiceInitiators.SMTMSPI_CLASS_NAME,
SpringServiceInitiators.PMSI_CLASS_NAME,
SpringServiceInitiators.BLFI_CLASS_NAME}) {
if (ClassUtils.isPresent(className, classLoader)) {
reflectionHints.registerType(TypeReference.of(className),
builder -> builder.withField(SpringServiceInitiators.SSI_FIELD_NAME));
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Args = -H:ServiceLoaderFeatureExcludeServices=org.hibernate.jpa.HibernatePersistenceProvider \
-H:ServiceLoaderFeatureExcludeServices=jakarta.persistence.spi.PersistenceProvider
3 changes: 2 additions & 1 deletion spring-orm/src/main/resources/META-INF/spring/aot.factories
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypesBeanRegistrationAotProcessor

org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.orm.jpa.EntityManagerRuntimeHints
org.springframework.orm.jpa.EntityManagerRuntimeHints,\
org.springframework.orm.jpa.vendor.SpringServiceInitiatorsRuntimeHints

0 comments on commit f367f7a

Please sign in to comment.