Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.0.1-SNAPSHOT</version>
<version>4.0.x-GH-4092-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data JPA Parent</name>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-envers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>4.0.1-SNAPSHOT</version>
<version>4.0.x-GH-4092-SNAPSHOT</version>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.0.1-SNAPSHOT</version>
<version>4.0.x-GH-4092-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-jpa-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.0.1-SNAPSHOT</version>
<version>4.0.x-GH-4092-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>4.0.1-SNAPSHOT</version>
<version>4.0.x-GH-4092-SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand All @@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.0.1-SNAPSHOT</version>
<version>4.0.x-GH-4092-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import jakarta.persistence.spi.PersistenceUnitInfo;

import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import org.springframework.data.repository.config.AotRepositoryContext;
Expand Down Expand Up @@ -60,11 +61,26 @@ private AotEntityManagerFactoryCreator(Supplier<EntityManagerFactory> factory, O
* @param repositoryContext repository context providing classes.
*/
public static AotEntityManagerFactoryCreator from(AotRepositoryContext repositoryContext) {
return from(repositoryContext, Map.of());
}

/**
* Create a {@code PersistenceUnitContext} from the given {@link AotRepositoryContext} using Jakarta
* Persistence-annotated classes.
* <p>
* The underlying {@link jakarta.persistence.metamodel.Metamodel} requires Hibernate to build metamodel information.
*
* @param repositoryContext repository context providing classes.
* @param jpaProperties JPA properties to apply.
* @since 4.0.1
*/
public static AotEntityManagerFactoryCreator from(AotRepositoryContext repositoryContext,
Map<String, Object> jpaProperties) {

List<String> typeNames = repositoryContext.getResolvedTypes().stream()
.filter(AotEntityManagerFactoryCreator::isJakartaAnnotated).map(Class::getName).toList();

return from(PersistenceManagedTypes.of(typeNames, List.of()), typeNames);
return from(PersistenceManagedTypes.of(typeNames, List.of()), typeNames, jpaProperties);
}

/**
Expand All @@ -75,7 +91,21 @@ public static AotEntityManagerFactoryCreator from(AotRepositoryContext repositor
* @param persistenceUnitInfo persistence unit info to use.
*/
public static AotEntityManagerFactoryCreator from(PersistenceUnitInfo persistenceUnitInfo) {
return from(() -> new AotMetamodel(persistenceUnitInfo), persistenceUnitInfo);
return from(persistenceUnitInfo, Map.of());
}

/**
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceUnitInfo}.
* <p>
* The underlying {@link jakarta.persistence.metamodel.Metamodel} requires Hibernate to build metamodel information.
*
* @param persistenceUnitInfo persistence unit info to use.
* @param jpaProperties JPA properties to apply.
* @since 4.0.1
*/
public static AotEntityManagerFactoryCreator from(PersistenceUnitInfo persistenceUnitInfo,
Map<String, Object> jpaProperties) {
return from(() -> new AotMetamodel(persistenceUnitInfo, jpaProperties), persistenceUnitInfo);
}

/**
Expand All @@ -86,11 +116,26 @@ public static AotEntityManagerFactoryCreator from(PersistenceUnitInfo persistenc
* @param managedTypes managed types to use.
*/
public static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes) {
return from(managedTypes, managedTypes);
return from(managedTypes, managedTypes, Map.of());
}

/**
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceManagedTypes}.
* <p>
* The underlying {@link jakarta.persistence.metamodel.Metamodel} requires Hibernate to build metamodel information.
*
* @param managedTypes managed types to use.
* @param jpaProperties JPA properties to apply.
* @since 4.0.1
*/
public static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes,
Map<String, Object> jpaProperties) {
return from(managedTypes, managedTypes, jpaProperties);
}

private static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes, Object cacheKey) {
return from(() -> new AotMetamodel(managedTypes), cacheKey);
private static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes, Object cacheKey,
Map<String, Object> jpaProperties) {
return from(() -> new AotMetamodel(managedTypes, jpaProperties), cacheKey);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
Expand Down Expand Up @@ -61,11 +62,12 @@ class AotMetamodel implements Metamodel {
private final Lazy<EntityManagerFactory> entityManagerFactory;
private final Lazy<EntityManager> entityManager = Lazy.of(() -> getEntityManagerFactory().createEntityManager());

public AotMetamodel(PersistenceManagedTypes managedTypes) {
this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl());
public AotMetamodel(PersistenceManagedTypes managedTypes, Map<String, Object> jpaProperties) {
this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl(), jpaProperties);
}

public AotMetamodel(Collection<String> managedTypes, @Nullable URL persistenceUnitRootUrl) {
public AotMetamodel(Collection<String> managedTypes, @Nullable URL persistenceUnitRootUrl,
Map<String, Object> jpaProperties) {

SpringPersistenceUnitInfo persistenceUnitInfo = new SpringPersistenceUnitInfo(
managedTypes.getClass().getClassLoader());
Expand All @@ -78,21 +80,26 @@ public AotMetamodel(Collection<String> managedTypes, @Nullable URL persistenceUn

persistenceUnitInfo.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName());
return new PersistenceUnitInfoDescriptor(persistenceUnitInfo.asStandardPersistenceUnitInfo());
});
}, jpaProperties);
}

public AotMetamodel(PersistenceUnitInfo unitInfo) {
this.entityManagerFactory = init(() -> new PersistenceUnitInfoDescriptor(unitInfo));
public AotMetamodel(PersistenceUnitInfo unitInfo, Map<String, Object> jpaProperties) {
this.entityManagerFactory = init(() -> new PersistenceUnitInfoDescriptor(unitInfo), jpaProperties);
}

static Lazy<EntityManagerFactory> init(Supplier<PersistenceUnitInfoDescriptor> unitInfo) {
static Lazy<EntityManagerFactory> init(Supplier<PersistenceUnitInfoDescriptor> unitInfo,
Map<String, Object> jpaProperties) {

return Lazy.of(() -> new EntityManagerFactoryBuilderImpl(unitInfo.get(),
Map.of(JdbcSettings.DIALECT, SpringDataJpaAotDialect.INSTANCE, //
JdbcSettings.ALLOW_METADATA_ON_BOOT, false, //
JdbcSettings.CONNECTION_PROVIDER, new UserSuppliedConnectionProviderImpl(), //
QuerySettings.QUERY_STARTUP_CHECKING, false, //
PersistenceSettings.JPA_CALLBACKS_ENABLED, false))
Map<String, Object> properties = new LinkedHashMap<>();
properties.putAll(Map.of(JdbcSettings.DIALECT, SpringDataJpaAotDialect.INSTANCE, //
JdbcSettings.ALLOW_METADATA_ON_BOOT, false, //
JdbcSettings.CONNECTION_PROVIDER, new UserSuppliedConnectionProviderImpl(), //
QuerySettings.QUERY_STARTUP_CHECKING, false, //
PersistenceSettings.JPA_CALLBACKS_ENABLED, false));

properties.putAll(jpaProperties);

return Lazy.of(() -> new EntityManagerFactoryBuilderImpl(unitInfo.get(), properties)
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.hibernate.cfg.MappingSettings;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -54,6 +57,8 @@
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.dao.DataAccessException;
Expand Down Expand Up @@ -403,6 +408,7 @@ protected void configureTypeContribution(Class<?> type, AotContext aotContext) {

ConfigurableListableBeanFactory beanFactory = repositoryContext.getBeanFactory();
Environment environment = repositoryContext.getEnvironment();
JpaProperties properties = new JpaProperties(environment);
boolean useEntityManager = environment.getProperty(USE_ENTITY_MANAGER, Boolean.class, false);

if (useEntityManager) {
Expand All @@ -426,7 +432,8 @@ protected void configureTypeContribution(Class<?> type, AotContext aotContext) {
if (managedTypes != null) {

log.debug("Using PersistenceManagedTypes for AOT repository generation");
return contribute(repositoryContext, AotEntityManagerFactoryCreator.from(managedTypes));
return contribute(repositoryContext,
AotEntityManagerFactoryCreator.from(managedTypes, properties.getJpaProperties()));
}

ObjectProvider<PersistenceUnitInfo> infoProvider = beanFactory.getBeanProvider(PersistenceUnitInfo.class);
Expand All @@ -435,11 +442,13 @@ protected void configureTypeContribution(Class<?> type, AotContext aotContext) {
if (unitInfo != null) {

log.debug("Using PersistenceUnitInfo for AOT repository generation");
return contribute(repositoryContext, AotEntityManagerFactoryCreator.from(unitInfo));
return contribute(repositoryContext,
AotEntityManagerFactoryCreator.from(unitInfo, properties.getJpaProperties()));
}

log.debug("Using scanned types for AOT repository generation");
return contribute(repositoryContext, AotEntityManagerFactoryCreator.from(repositoryContext));
return contribute(repositoryContext,
AotEntityManagerFactoryCreator.from(repositoryContext, properties.getJpaProperties()));
}

private JpaRepositoryContributor contribute(AotRepositoryContext repositoryContext,
Expand All @@ -449,4 +458,51 @@ private JpaRepositoryContributor contribute(AotRepositoryContext repositoryConte

}

static class JpaProperties {

private final Map<String, Object> jpaProperties;

public JpaProperties(Environment environment) {

this.jpaProperties = new LinkedHashMap<>();

String implicitStrategy = environment.getProperty("spring.jpa.hibernate.naming.implicitStrategy");
if (implicitStrategy == null) {
implicitStrategy = environment.getProperty("spring.jpa.hibernate.naming.implicit-strategy");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we flip this around? IIRC, the kebab-case notation is the preferred one in recent Boot versions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal is functional only, wanted to collect a few thoughts about the general approach. Turning things into order and the final polishing would be the next step after getting to overall consensus that this is the path forward.

}
if (StringUtils.hasText(implicitStrategy)) {
jpaProperties.put(MappingSettings.IMPLICIT_NAMING_STRATEGY, implicitStrategy);
}

String physicalStrategy = environment.getProperty("spring.jpa.hibernate.naming.physicalStrategy");
if (physicalStrategy == null) {
physicalStrategy = environment.getProperty("spring.jpa.hibernate.naming.physical-strategy");
}
if (StringUtils.hasText(physicalStrategy)) {
jpaProperties.put(MappingSettings.PHYSICAL_NAMING_STRATEGY, physicalStrategy);
}

if (environment instanceof ConfigurableEnvironment ce) {

ce.getPropertySources().forEach(propertySource -> {

if (propertySource instanceof EnumerablePropertySource<?> eps) {

String prefix = "spring.jpa.properties.";
Map<String, Object> partialProperties = Stream.of(eps.getPropertyNames())
.filter(propertyName -> propertyName.startsWith(prefix))
.collect(Collectors.toMap(k -> k.substring(prefix.length()), propertySource::getProperty));

jpaProperties.putAll(partialProperties);
}
});
}
}

public Map<String, Object> getJpaProperties() {
return jpaProperties;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

import org.hibernate.cfg.MappingSettings;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
Expand All @@ -38,9 +40,11 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension.JpaProperties;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.instrument.classloading.ShadowingClassLoader;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;

/**
Expand Down Expand Up @@ -149,6 +153,40 @@ void exposesJpaAotProcessor() {
.isEqualTo(JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor.class);
}

@Test // GH-4092
void collectsWellKnownPropertyNames() {

MockEnvironment environment = new MockEnvironment()
.withProperty("spring.jpa.hibernate.naming.implicit-strategy", "Implicit")
.withProperty("spring.jpa.hibernate.naming.physical-strategy", "Physical");

Map<String, Object> jpaProperties = new JpaProperties(environment).getJpaProperties();

assertThat(jpaProperties).containsEntry(MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit");
assertThat(jpaProperties).containsEntry(MappingSettings.PHYSICAL_NAMING_STRATEGY, "Physical");

environment = new MockEnvironment().withProperty("spring.jpa.hibernate.naming.implicitStrategy", "Implicit")
.withProperty("spring.jpa.hibernate.naming.physicalStrategy", "Physical");

jpaProperties = new JpaProperties(environment).getJpaProperties();

assertThat(jpaProperties).containsEntry(MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit");
assertThat(jpaProperties).containsEntry(MappingSettings.PHYSICAL_NAMING_STRATEGY, "Physical");
}

@Test // GH-4092
void collectsJpaPropertyNames() {

MockEnvironment environment = new MockEnvironment()
.withProperty("spring.jpa.properties." + MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit")
.withProperty("spring.jpa.properties.foo", "bar");

Map<String, Object> jpaProperties = new JpaProperties(environment).getJpaProperties();

assertThat(jpaProperties).containsEntry(MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit");
assertThat(jpaProperties).containsEntry("foo", "bar");
}

private void assertOnlyOnePersistenceAnnotationBeanPostProcessorRegistered(DefaultListableBeanFactory factory,
String expectedBeanName) {

Expand Down
Loading