From 40300908eab638b4d5808cad32b93d77a4cdd920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 5 Aug 2024 11:53:42 +0200 Subject: [PATCH] Include vendor properties in auto-configured EntityManagerFactoryBuilder This commit moves the setup of vendor properties (e.g. Hibernate) from the auto-configured LocalContainerEntityManagerFactoryBean to the auto-configured EntityManagerFactoryBuilder. This way, custom use of the latter retains additional auto-configuration logic such as the naming strategy and DDL mode to use. Closes gh-15318 --- .../orm/jpa/JpaBaseConfiguration.java | 16 +++++++++---- .../AbstractJpaAutoConfigurationTests.java | 24 +++++++++++++++++++ .../modules/how-to/pages/data-access.adoc | 4 ++-- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index 3916e51929..bf6a6ceff7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -119,22 +120,27 @@ public abstract class JpaBaseConfiguration { public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, ObjectProvider persistenceUnitManager, ObjectProvider customizers) { - EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, - this.properties.getProperties(), persistenceUnitManager.getIfAvailable()); + EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, buildJpaProperties(), + persistenceUnitManager.getIfAvailable()); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder; } + private Map buildJpaProperties() { + Map properties = new HashMap<>(this.properties.getProperties()); + Map vendorProperties = getVendorProperties(); + customizeVendorProperties(vendorProperties); + properties.putAll(vendorProperties); + return properties; + } + @Bean @Primary @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class }) public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder, PersistenceManagedTypes persistenceManagedTypes) { - Map vendorProperties = getVendorProperties(); - customizeVendorProperties(vendorProperties); return factoryBuilder.dataSource(this.dataSource) .managedTypes(persistenceManagedTypes) - .properties(vendorProperties) .mappingResources(getMappingResources()) .jta(isJta()) .build(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java index d5efec995d..95b1f92f14 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java @@ -41,6 +41,7 @@ import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConf import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -207,6 +208,18 @@ abstract class AbstractJpaAutoConfigurationTests { }); } + @Test + void usesManuallyDefinedLocalContainerEntityManagerFactoryBeanUsingBuilder() { + this.contextRunner.withPropertyValues("spring.jpa.properties.a=b") + .withUserConfiguration(TestConfigurationWithEntityManagerFactoryBuilder.class) + .run((context) -> { + LocalContainerEntityManagerFactoryBean factoryBean = context + .getBean(LocalContainerEntityManagerFactoryBean.class); + Map map = factoryBean.getJpaPropertyMap(); + assertThat(map).containsEntry("configured", "manually").containsEntry("a", "b"); + }); + } + @Test void usesManuallyDefinedLocalContainerEntityManagerFactoryBeanIfAvailable() { this.contextRunner.withUserConfiguration(TestConfigurationWithLocalContainerEntityManagerFactoryBean.class) @@ -380,6 +393,17 @@ abstract class AbstractJpaAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TestConfigurationWithEntityManagerFactoryBuilder extends TestConfiguration { + + @Bean + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder, + DataSource dataSource) { + return builder.dataSource(dataSource).properties(Map.of("configured", "manually")).build(); + } + + } + @Configuration(proxyBeanMethods = false) static class TestConfigurationWithLocalContainerEntityManagerFactoryBean extends TestConfiguration { diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc index cfc0cb8348..a85f619178 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc @@ -311,8 +311,8 @@ It scans entities located in the same package as `Order`. It is possible to map additional JPA properties using the `app.first.jpa` namespace. NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost. -For example, in the case of Hibernate, any properties under the `spring.jpa.hibernate` prefix will not be automatically applied to your `LocalContainerEntityManagerFactoryBean`. -If you were relying on these properties for configuring things like the naming strategy or the DDL mode, you will need to explicitly configure that when creating the `LocalContainerEntityManagerFactoryBean` bean. +Make sure to use the auto-configured `EntityManagerFactoryBuilder` to retain JPA and vendor properties. +This is particularly important if you were relying on `spring.jpa.*` properties for configuring things like the naming strategy or the DDL mode. You should provide a similar configuration for any additional data sources for which you need JPA access. To complete the picture, you need to configure a `JpaTransactionManager` for each `EntityManagerFactory` as well.