Skip to content

Commit 49d85d5

Browse files
committed
Propagate well-known property JPA names to AOT metamodel initialization.
1 parent 554fd3a commit 49d85d5

File tree

4 files changed

+167
-21
lines changed

4 files changed

+167
-21
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotEntityManagerFactoryCreator.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import jakarta.persistence.spi.PersistenceUnitInfo;
2424

2525
import java.util.List;
26+
import java.util.Map;
2627
import java.util.function.Supplier;
2728

2829
import org.springframework.data.repository.config.AotRepositoryContext;
@@ -60,11 +61,26 @@ private AotEntityManagerFactoryCreator(Supplier<EntityManagerFactory> factory, O
6061
* @param repositoryContext repository context providing classes.
6162
*/
6263
public static AotEntityManagerFactoryCreator from(AotRepositoryContext repositoryContext) {
64+
return from(repositoryContext, Map.of());
65+
}
66+
67+
/**
68+
* Create a {@code PersistenceUnitContext} from the given {@link AotRepositoryContext} using Jakarta
69+
* Persistence-annotated classes.
70+
* <p>
71+
* The underlying {@link jakarta.persistence.metamodel.Metamodel} requires Hibernate to build metamodel information.
72+
*
73+
* @param repositoryContext repository context providing classes.
74+
* @param jpaProperties JPA properties to apply.
75+
* @since 4.0.1
76+
*/
77+
public static AotEntityManagerFactoryCreator from(AotRepositoryContext repositoryContext,
78+
Map<String, Object> jpaProperties) {
6379

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

67-
return from(PersistenceManagedTypes.of(typeNames, List.of()), typeNames);
83+
return from(PersistenceManagedTypes.of(typeNames, List.of()), typeNames, jpaProperties);
6884
}
6985

7086
/**
@@ -75,7 +91,21 @@ public static AotEntityManagerFactoryCreator from(AotRepositoryContext repositor
7591
* @param persistenceUnitInfo persistence unit info to use.
7692
*/
7793
public static AotEntityManagerFactoryCreator from(PersistenceUnitInfo persistenceUnitInfo) {
78-
return from(() -> new AotMetamodel(persistenceUnitInfo), persistenceUnitInfo);
94+
return from(persistenceUnitInfo, Map.of());
95+
}
96+
97+
/**
98+
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceUnitInfo}.
99+
* <p>
100+
* The underlying {@link jakarta.persistence.metamodel.Metamodel} requires Hibernate to build metamodel information.
101+
*
102+
* @param persistenceUnitInfo persistence unit info to use.
103+
* @param jpaProperties JPA properties to apply.
104+
* @since 4.0.1
105+
*/
106+
public static AotEntityManagerFactoryCreator from(PersistenceUnitInfo persistenceUnitInfo,
107+
Map<String, Object> jpaProperties) {
108+
return from(() -> new AotMetamodel(persistenceUnitInfo, jpaProperties), persistenceUnitInfo);
79109
}
80110

81111
/**
@@ -86,11 +116,26 @@ public static AotEntityManagerFactoryCreator from(PersistenceUnitInfo persistenc
86116
* @param managedTypes managed types to use.
87117
*/
88118
public static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes) {
89-
return from(managedTypes, managedTypes);
119+
return from(managedTypes, managedTypes, Map.of());
120+
}
121+
122+
/**
123+
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceManagedTypes}.
124+
* <p>
125+
* The underlying {@link jakarta.persistence.metamodel.Metamodel} requires Hibernate to build metamodel information.
126+
*
127+
* @param managedTypes managed types to use.
128+
* @param jpaProperties JPA properties to apply.
129+
* @since 4.0.1
130+
*/
131+
public static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes,
132+
Map<String, Object> jpaProperties) {
133+
return from(managedTypes, managedTypes, jpaProperties);
90134
}
91135

92-
private static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes, Object cacheKey) {
93-
return from(() -> new AotMetamodel(managedTypes), cacheKey);
136+
private static AotEntityManagerFactoryCreator from(PersistenceManagedTypes managedTypes, Object cacheKey,
137+
Map<String, Object> jpaProperties) {
138+
return from(() -> new AotMetamodel(managedTypes, jpaProperties), cacheKey);
94139
}
95140

96141
/**

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotMetamodel.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import java.net.URL;
2828
import java.util.Collection;
29+
import java.util.LinkedHashMap;
2930
import java.util.Map;
3031
import java.util.Set;
3132
import java.util.function.Supplier;
@@ -61,11 +62,12 @@ class AotMetamodel implements Metamodel {
6162
private final Lazy<EntityManagerFactory> entityManagerFactory;
6263
private final Lazy<EntityManager> entityManager = Lazy.of(() -> getEntityManagerFactory().createEntityManager());
6364

64-
public AotMetamodel(PersistenceManagedTypes managedTypes) {
65-
this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl());
65+
public AotMetamodel(PersistenceManagedTypes managedTypes, Map<String, Object> jpaProperties) {
66+
this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl(), jpaProperties);
6667
}
6768

68-
public AotMetamodel(Collection<String> managedTypes, @Nullable URL persistenceUnitRootUrl) {
69+
public AotMetamodel(Collection<String> managedTypes, @Nullable URL persistenceUnitRootUrl,
70+
Map<String, Object> jpaProperties) {
6971

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

7981
persistenceUnitInfo.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName());
8082
return new PersistenceUnitInfoDescriptor(persistenceUnitInfo.asStandardPersistenceUnitInfo());
81-
});
83+
}, jpaProperties);
8284
}
8385

84-
public AotMetamodel(PersistenceUnitInfo unitInfo) {
85-
this.entityManagerFactory = init(() -> new PersistenceUnitInfoDescriptor(unitInfo));
86+
public AotMetamodel(PersistenceUnitInfo unitInfo, Map<String, Object> jpaProperties) {
87+
this.entityManagerFactory = init(() -> new PersistenceUnitInfoDescriptor(unitInfo), jpaProperties);
8688
}
8789

88-
static Lazy<EntityManagerFactory> init(Supplier<PersistenceUnitInfoDescriptor> unitInfo) {
90+
static Lazy<EntityManagerFactory> init(Supplier<PersistenceUnitInfoDescriptor> unitInfo,
91+
Map<String, Object> jpaProperties) {
8992

90-
return Lazy.of(() -> new EntityManagerFactoryBuilderImpl(unitInfo.get(),
91-
Map.of(JdbcSettings.DIALECT, SpringDataJpaAotDialect.INSTANCE, //
92-
JdbcSettings.ALLOW_METADATA_ON_BOOT, false, //
93-
JdbcSettings.CONNECTION_PROVIDER, new UserSuppliedConnectionProviderImpl(), //
94-
QuerySettings.QUERY_STARTUP_CHECKING, false, //
95-
PersistenceSettings.JPA_CALLBACKS_ENABLED, false))
93+
Map<String, Object> properties = new LinkedHashMap<>();
94+
properties.putAll(Map.of(JdbcSettings.DIALECT, SpringDataJpaAotDialect.INSTANCE, //
95+
JdbcSettings.ALLOW_METADATA_ON_BOOT, false, //
96+
JdbcSettings.CONNECTION_PROVIDER, new UserSuppliedConnectionProviderImpl(), //
97+
QuerySettings.QUERY_STARTUP_CHECKING, false, //
98+
PersistenceSettings.JPA_CALLBACKS_ENABLED, false));
99+
100+
properties.putAll(jpaProperties);
101+
102+
return Lazy.of(() -> new EntityManagerFactoryBuilderImpl(unitInfo.get(), properties)
96103
.build());
97104
}
98105

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import java.util.Map;
3636
import java.util.Optional;
3737
import java.util.Set;
38+
import java.util.stream.Collectors;
39+
import java.util.stream.Stream;
3840

41+
import org.hibernate.cfg.MappingSettings;
3942
import org.jspecify.annotations.Nullable;
4043
import org.slf4j.Logger;
4144
import org.slf4j.LoggerFactory;
@@ -54,6 +57,8 @@
5457
import org.springframework.context.ConfigurableApplicationContext;
5558
import org.springframework.context.annotation.AnnotationConfigUtils;
5659
import org.springframework.core.annotation.AnnotationAttributes;
60+
import org.springframework.core.env.ConfigurableEnvironment;
61+
import org.springframework.core.env.EnumerablePropertySource;
5762
import org.springframework.core.env.Environment;
5863
import org.springframework.core.io.ResourceLoader;
5964
import org.springframework.dao.DataAccessException;
@@ -403,6 +408,7 @@ protected void configureTypeContribution(Class<?> type, AotContext aotContext) {
403408

404409
ConfigurableListableBeanFactory beanFactory = repositoryContext.getBeanFactory();
405410
Environment environment = repositoryContext.getEnvironment();
411+
JpaProperties properties = new JpaProperties(environment);
406412
boolean useEntityManager = environment.getProperty(USE_ENTITY_MANAGER, Boolean.class, false);
407413

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

428434
log.debug("Using PersistenceManagedTypes for AOT repository generation");
429-
return contribute(repositoryContext, AotEntityManagerFactoryCreator.from(managedTypes));
435+
return contribute(repositoryContext,
436+
AotEntityManagerFactoryCreator.from(managedTypes, properties.getJpaProperties()));
430437
}
431438

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

437444
log.debug("Using PersistenceUnitInfo for AOT repository generation");
438-
return contribute(repositoryContext, AotEntityManagerFactoryCreator.from(unitInfo));
445+
return contribute(repositoryContext,
446+
AotEntityManagerFactoryCreator.from(unitInfo, properties.getJpaProperties()));
439447
}
440448

441449
log.debug("Using scanned types for AOT repository generation");
442-
return contribute(repositoryContext, AotEntityManagerFactoryCreator.from(repositoryContext));
450+
return contribute(repositoryContext,
451+
AotEntityManagerFactoryCreator.from(repositoryContext, properties.getJpaProperties()));
443452
}
444453

445454
private JpaRepositoryContributor contribute(AotRepositoryContext repositoryContext,
@@ -449,4 +458,51 @@ private JpaRepositoryContributor contribute(AotRepositoryContext repositoryConte
449458

450459
}
451460

461+
static class JpaProperties {
462+
463+
private final Map<String, Object> jpaProperties;
464+
465+
public JpaProperties(Environment environment) {
466+
467+
this.jpaProperties = new LinkedHashMap<>();
468+
469+
String implicitStrategy = environment.getProperty("spring.jpa.hibernate.naming.implicitStrategy");
470+
if (implicitStrategy == null) {
471+
implicitStrategy = environment.getProperty("spring.jpa.hibernate.naming.implicit-strategy");
472+
}
473+
if (StringUtils.hasText(implicitStrategy)) {
474+
jpaProperties.put(MappingSettings.IMPLICIT_NAMING_STRATEGY, implicitStrategy);
475+
}
476+
477+
String physicalStrategy = environment.getProperty("spring.jpa.hibernate.naming.physicalStrategy");
478+
if (physicalStrategy == null) {
479+
physicalStrategy = environment.getProperty("spring.jpa.hibernate.naming.physical-strategy");
480+
}
481+
if (StringUtils.hasText(physicalStrategy)) {
482+
jpaProperties.put(MappingSettings.PHYSICAL_NAMING_STRATEGY, physicalStrategy);
483+
}
484+
485+
if (environment instanceof ConfigurableEnvironment ce) {
486+
487+
ce.getPropertySources().forEach(propertySource -> {
488+
489+
if (propertySource instanceof EnumerablePropertySource<?> eps) {
490+
491+
String prefix = "spring.jpa.properties.";
492+
Map<String, Object> partialProperties = Stream.of(eps.getPropertyNames())
493+
.filter(propertyName -> propertyName.startsWith(prefix))
494+
.collect(Collectors.toMap(k -> k.substring(prefix.length()), propertySource::getProperty));
495+
496+
jpaProperties.putAll(partialProperties);
497+
}
498+
});
499+
}
500+
}
501+
502+
public Map<String, Object> getJpaProperties() {
503+
return jpaProperties;
504+
}
505+
506+
}
507+
452508
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323

2424
import java.util.Arrays;
2525
import java.util.Collections;
26+
import java.util.Map;
2627

28+
import org.hibernate.cfg.MappingSettings;
2729
import org.junit.jupiter.api.Test;
2830
import org.junit.jupiter.api.extension.ExtendWith;
2931
import org.mockito.Mock;
@@ -38,9 +40,11 @@
3840
import org.springframework.context.ApplicationContext;
3941
import org.springframework.context.annotation.AnnotationConfigUtils;
4042
import org.springframework.context.support.GenericApplicationContext;
43+
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension.JpaProperties;
4144
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
4245
import org.springframework.data.repository.config.RepositoryConfigurationSource;
4346
import org.springframework.instrument.classloading.ShadowingClassLoader;
47+
import org.springframework.mock.env.MockEnvironment;
4448
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
4549

4650
/**
@@ -149,6 +153,40 @@ void exposesJpaAotProcessor() {
149153
.isEqualTo(JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor.class);
150154
}
151155

156+
@Test // GH-4092
157+
void collectsWellKnownPropertyNames() {
158+
159+
MockEnvironment environment = new MockEnvironment()
160+
.withProperty("spring.jpa.hibernate.naming.implicit-strategy", "Implicit")
161+
.withProperty("spring.jpa.hibernate.naming.physical-strategy", "Physical");
162+
163+
Map<String, Object> jpaProperties = new JpaProperties(environment).getJpaProperties();
164+
165+
assertThat(jpaProperties).containsEntry(MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit");
166+
assertThat(jpaProperties).containsEntry(MappingSettings.PHYSICAL_NAMING_STRATEGY, "Physical");
167+
168+
environment = new MockEnvironment().withProperty("spring.jpa.hibernate.naming.implicitStrategy", "Implicit")
169+
.withProperty("spring.jpa.hibernate.naming.physicalStrategy", "Physical");
170+
171+
jpaProperties = new JpaProperties(environment).getJpaProperties();
172+
173+
assertThat(jpaProperties).containsEntry(MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit");
174+
assertThat(jpaProperties).containsEntry(MappingSettings.PHYSICAL_NAMING_STRATEGY, "Physical");
175+
}
176+
177+
@Test // GH-4092
178+
void collectsJpaPropertyNames() {
179+
180+
MockEnvironment environment = new MockEnvironment()
181+
.withProperty("spring.jpa.properties." + MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit")
182+
.withProperty("spring.jpa.properties.foo", "bar");
183+
184+
Map<String, Object> jpaProperties = new JpaProperties(environment).getJpaProperties();
185+
186+
assertThat(jpaProperties).containsEntry(MappingSettings.IMPLICIT_NAMING_STRATEGY, "Implicit");
187+
assertThat(jpaProperties).containsEntry("foo", "bar");
188+
}
189+
152190
private void assertOnlyOnePersistenceAnnotationBeanPostProcessorRegistered(DefaultListableBeanFactory factory,
153191
String expectedBeanName) {
154192

0 commit comments

Comments
 (0)