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
This commit is contained in:
Stéphane Nicoll
2024-08-05 11:53:42 +02:00
parent 4eebb8e629
commit 40300908ea
3 changed files with 37 additions and 7 deletions

View File

@@ -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> persistenceUnitManager,
ObjectProvider<EntityManagerFactoryBuilderCustomizer> 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<String, ?> buildJpaProperties() {
Map<String, Object> properties = new HashMap<>(this.properties.getProperties());
Map<String, Object> vendorProperties = getVendorProperties();
customizeVendorProperties(vendorProperties);
properties.putAll(vendorProperties);
return properties;
}
@Bean
@Primary
@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder,
PersistenceManagedTypes persistenceManagedTypes) {
Map<String, Object> vendorProperties = getVendorProperties();
customizeVendorProperties(vendorProperties);
return factoryBuilder.dataSource(this.dataSource)
.managedTypes(persistenceManagedTypes)
.properties(vendorProperties)
.mappingResources(getMappingResources())
.jta(isJta())
.build();

View File

@@ -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<String, Object> 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 {

View File

@@ -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.