diff --git a/pom.xml b/pom.xml index 8bbfd5c919..538efe7154 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.1-SNAPSHOT + 4.0.x-GH-4092-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index b0c76f2722..dc17019309 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 4.0.1-SNAPSHOT + 4.0.x-GH-4092-SNAPSHOT org.springframework.data spring-data-jpa-parent - 4.0.1-SNAPSHOT + 4.0.x-GH-4092-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 283893c043..1fe05c86c6 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.1-SNAPSHOT + 4.0.x-GH-4092-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index d5cf754246..c66fd78347 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-jpa - 4.0.1-SNAPSHOT + 4.0.x-GH-4092-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -16,7 +16,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.1-SNAPSHOT + 4.0.x-GH-4092-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotEntityManagerFactoryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotEntityManagerFactoryCreator.java index 26131f9ed1..66f1768f38 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotEntityManagerFactoryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotEntityManagerFactoryCreator.java @@ -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; @@ -60,11 +61,26 @@ private AotEntityManagerFactoryCreator(Supplier 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. + *

+ * 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 jpaProperties) { List 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); } /** @@ -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}. + *

+ * 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 jpaProperties) { + return from(() -> new AotMetamodel(persistenceUnitInfo, jpaProperties), persistenceUnitInfo); } /** @@ -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}. + *

+ * 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 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 jpaProperties) { + return from(() -> new AotMetamodel(managedTypes, jpaProperties), cacheKey); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotMetamodel.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotMetamodel.java index 751bf19d3a..ff1e3307a6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotMetamodel.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotMetamodel.java @@ -43,10 +43,16 @@ import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; import org.hibernate.query.common.TemporalUnit; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.util.Lazy; import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes; import org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo; +import org.springframework.util.CollectionUtils; /** * AOT metamodel implementation that uses Hibernate to build the metamodel. @@ -58,14 +64,26 @@ */ class AotMetamodel implements Metamodel { + private static final Logger log = LoggerFactory.getLogger(AotMetamodel.class); + + /** + * Collection of know properties causing problems during AOT if set differntly + */ + private static final Map FAILSAFE_AOT_PROPERTIES = Map.of( // + JdbcSettings.ALLOW_METADATA_ON_BOOT, false, // + JdbcSettings.CONNECTION_PROVIDER, NoOpConnectionProvider.INSTANCE, // + QuerySettings.QUERY_STARTUP_CHECKING, false, // + PersistenceSettings.JPA_CALLBACKS_ENABLED, false // + ); private final Lazy entityManagerFactory; private final Lazy entityManager = Lazy.of(() -> getEntityManagerFactory().createEntityManager()); - public AotMetamodel(PersistenceManagedTypes managedTypes) { - this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl()); + public AotMetamodel(PersistenceManagedTypes managedTypes, Map jpaProperties) { + this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl(), jpaProperties); } - public AotMetamodel(Collection managedTypes, @Nullable URL persistenceUnitRootUrl) { + public AotMetamodel(Collection managedTypes, @Nullable URL persistenceUnitRootUrl, + Map jpaProperties) { SpringPersistenceUnitInfo persistenceUnitInfo = new SpringPersistenceUnitInfo( managedTypes.getClass().getClassLoader()); @@ -78,22 +96,46 @@ public AotMetamodel(Collection managedTypes, @Nullable URL persistenceUn persistenceUnitInfo.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName()); return new PersistenceUnitInfoDescriptor(persistenceUnitInfo.asStandardPersistenceUnitInfo()); - }); + }, jpaProperties); + } + + public AotMetamodel(PersistenceUnitInfo unitInfo, Map jpaProperties) { + this.entityManagerFactory = init(() -> new PersistenceUnitInfoDescriptor(unitInfo), jpaProperties); + } + + static Lazy init(Supplier unitInfo, + Map jpaProperties) { + return Lazy.of(() -> new EntityManagerFactoryBuilderImpl(unitInfo.get(), initProperties(jpaProperties)).build()); } - public AotMetamodel(PersistenceUnitInfo unitInfo) { - this.entityManagerFactory = init(() -> new PersistenceUnitInfoDescriptor(unitInfo)); + static Map initProperties(Map jpaProperties) { + + Map properties = CollectionUtils + .newLinkedHashMap(jpaProperties.size() + FAILSAFE_AOT_PROPERTIES.size() + 1); + + // we allow explicit Dialect Overrides, but put in a default one to avoid potential db access + properties.put(JdbcSettings.DIALECT, SpringDataJpaAotDialect.INSTANCE); + + // apply user defined properties + properties.putAll(jpaProperties); + + // override properties known to cause trouble + applyPropertyOverrides(properties); + + return properties; } - static Lazy init(Supplier unitInfo) { + private static void applyPropertyOverrides(Map properties) { + + for (Map.Entry entry : FAILSAFE_AOT_PROPERTIES.entrySet()) { - 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)) - .build()); + if (log.isDebugEnabled() && properties.containsKey(entry.getKey())) { + log.debug("Overriding property [%s] with value [%s] for AOT Repository processing.".formatted(entry.getKey(), + entry.getValue())); + } + + properties.put(entry.getKey(), entry.getValue()); + } } private Metamodel getMetamodel() { @@ -141,6 +183,7 @@ public EntityManagerFactory getEntityManagerFactory() { * A {@link Dialect} to satisfy the bootstrap requirements of {@link JdbcSettings#DIALECT} during the AOT Phase. Printed * to log files (info level) when the {@link org.hibernate.engine.jdbc.env.spi.JdbcEnvironment} is created. */ + @NullUnmarked @SuppressWarnings("deprecation") static class SpringDataJpaAotDialect extends Dialect { @@ -164,6 +207,12 @@ public SequenceSupport getSequenceSupport() { return ANSISequenceSupport.INSTANCE; } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + // javadoc implies null would trigger default which is not the case + return new StandardSqlAstTranslatorFactory(); + } + @Override @SuppressWarnings("deprecation") public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { @@ -175,4 +224,14 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT } + static class NoOpConnectionProvider extends UserSuppliedConnectionProviderImpl { + + static final NoOpConnectionProvider INSTANCE = new NoOpConnectionProvider(); + + @Override + public String toString() { + return "NoOpConnectionProvider"; + } + } + } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotRepositoryFragmentSupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotRepositoryFragmentSupport.java index 3f0d2de2f7..545bb2f0d7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotRepositoryFragmentSupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotRepositoryFragmentSupport.java @@ -169,6 +169,7 @@ protected String rewriteQuery(DeclaredQuery query, Sort sort, Class returnedT return source; } + @SuppressWarnings("NullAway") protected long getCount(Query query) { List totals = query.getResultList(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaCodeBlocks.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaCodeBlocks.java index d90ecbc1e1..d9d4b9715a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaCodeBlocks.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaCodeBlocks.java @@ -645,7 +645,7 @@ public CodeBlock build() { TypeName typeToRead = isProjecting ? methodReturn.getActualTypeName() : TypeName.get(context.getDomainType()); builder.add("\n"); - if (modifying.isPresent() && !aotQuery.isDerived()) { + if (modifying.isPresent() && aotQuery !=null && !aotQuery.isDerived()) { if (modifying.getBoolean("flushAutomatically")) { builder.addStatement("this.$L.flush()", context.fieldNameOf(EntityManager.class)); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index bd55bf3ec0..9090b07bc0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -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; @@ -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; @@ -419,6 +424,7 @@ protected void configureTypeContribution(Class type, AotContext aotContext) { return new JpaRepositoryContributor(repositoryContext, emf); } + JpaProperties properties = new JpaProperties(environment); ObjectProvider managedTypesProvider = beanFactory .getBeanProvider(PersistenceManagedTypes.class); PersistenceManagedTypes managedTypes = managedTypesProvider.getIfUnique(); @@ -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 infoProvider = beanFactory.getBeanProvider(PersistenceUnitInfo.class); @@ -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, @@ -449,4 +458,59 @@ private JpaRepositoryContributor contribute(AotRepositoryContext repositoryConte } + static class JpaProperties { + + private final Map jpaProperties; + + public JpaProperties(Environment environment) { + + this.jpaProperties = new LinkedHashMap<>(); + + String implicitStrategy = getFirstAvailable(environment, "spring.jpa.hibernate.naming.implicitStrategy", + "spring.jpa.hibernate.naming.implicit-strategy"); + if (StringUtils.hasText(implicitStrategy)) { + jpaProperties.put(MappingSettings.IMPLICIT_NAMING_STRATEGY, implicitStrategy); + } + + String physicalStrategy = getFirstAvailable(environment, "spring.jpa.hibernate.naming.physicalStrategy", + "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 partialProperties = Stream.of(eps.getPropertyNames()) + .filter(propertyName -> propertyName.startsWith(prefix)) + .collect(Collectors.toMap(k -> k.substring(prefix.length()), propertySource::getProperty)); + + jpaProperties.putAll(partialProperties); + } + }); + } + } + + @Nullable + String getFirstAvailable(Environment environment, String... propertyNames) { + + for (String propertyName : propertyNames) { + String value = environment.getProperty(propertyName); + if (value != null) { + return value; + } + } + return null; + } + + public Map getJpaProperties() { + return jpaProperties; + } + + } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/AotMetamodelUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/AotMetamodelUnitTests.java index 9cfa3fd1d2..e9f191f211 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/AotMetamodelUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/AotMetamodelUnitTests.java @@ -15,14 +15,19 @@ */ package org.springframework.data.jpa.repository.aot; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; import org.junit.jupiter.api.Test; +import org.springframework.data.jpa.repository.aot.AotMetamodel.NoOpConnectionProvider; +import org.springframework.data.jpa.repository.aot.AotMetamodel.SpringDataJpaAotDialect; /** * Unit tests for {@link AotMetamodel}. * * @author Mark Paluch + * @author Christoph Strobl */ class AotMetamodelUnitTests { @@ -31,4 +36,36 @@ void dialectSupportsSequences() { assertThat(AotMetamodel.SpringDataJpaAotDialect.INSTANCE.getSequenceSupport().supportsSequences()).isTrue(); assertThat(AotMetamodel.SpringDataJpaAotDialect.INSTANCE.getSequenceSupport().supportsPooledSequences()).isTrue(); } + + @Test // GH-4092 + void intializesPropertiesWithDefaults() { + + assertThat(AotMetamodel.initProperties(Map.of())) // + .containsEntry("hibernate.dialect", SpringDataJpaAotDialect.INSTANCE) // + .containsEntry("hibernate.boot.allow_jdbc_metadata_access", false) // + .containsEntry("hibernate.connection.provider_class", NoOpConnectionProvider.INSTANCE) // + .containsEntry("hibernate.jpa_callbacks.enabled", false) // + .containsEntry("hibernate.query.startup_check", false); + } + + @Test // GH-4092 + void allowsDialectOverridesViaProperties() { + + assertThat(AotMetamodel.initProperties(Map.of("hibernate.dialect", "H2Dialect", "jpa.bla.bla", "42"))) + .containsEntry("hibernate.dialect", "H2Dialect") // + .containsEntry("jpa.bla.bla", "42"); + } + + @Test // GH-4092 + void preventsPropertyOverrides/* for cases we know cause trouble */() { + + assertThat(AotMetamodel.initProperties(Map.of(// + "hibernate.boot.allow_jdbc_metadata_access", "true", // + "hibernate.connection.provider_class", "DatasourceConnectionProviderImpl", // + "hibernate.jpa_callbacks.enabled", "true", // + "hibernate.query.startup_check", "true"))).containsEntry("hibernate.boot.allow_jdbc_metadata_access", false) // + .containsEntry("hibernate.connection.provider_class", NoOpConnectionProvider.INSTANCE) // + .containsEntry("hibernate.jpa_callbacks.enabled", false) // + .containsEntry("hibernate.query.startup_check", false); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java index 6412c70f88..89c64c0f62 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java @@ -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; @@ -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; /** @@ -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 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 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) {