Adapt EntityManagerFactoryBuilder to work with multiple data sources

This commit allows EntityManagerFactoryBuilder to provide the JPA
properties to use according to the DataSource used to build the
EntityManagerFactory. Previously the JPA properties were computed only
once based on the primary data source, which was a problem since its
default DDL setting may be different.

EntityManagerFactoryBuilder takes a function that provides the JPA
properties based on a data source, rather than the properties
themselves. Constructors with the previous variant have been deprecated
as a result.

Closes gh-44516
This commit is contained in:
Stéphane Nicoll 2025-03-06 11:22:03 +01:00
parent d097870de5
commit e06244d007
10 changed files with 362 additions and 47 deletions

View File

@ -16,8 +16,8 @@
package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import javax.sql.DataSource;
@ -194,9 +194,10 @@ class HibernateMetricsAutoConfigurationTests {
}
private LocalContainerEntityManagerFactoryBean createSessionFactory(DataSource ds) {
Map<String, String> jpaProperties = new HashMap<>();
jpaProperties.put("hibernate.generate_statistics", "true");
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaProperties, null).dataSource(ds)
Function<DataSource, Map<String, ?>> jpaPropertiesFactory = (dataSource) -> Map
.of("hibernate.generate_statistics", "true");
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaPropertiesFactory, null)
.dataSource(ds)
.packages(PACKAGE_CLASSES)
.build();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -138,8 +138,8 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration {
}
@Override
protected Map<String, Object> getVendorProperties() {
Supplier<String> defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(getDataSource());
protected Map<String, Object> getVendorProperties(DataSource dataSource) {
Supplier<String> defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(dataSource);
return new LinkedHashMap<>(this.hibernateProperties.determineHibernateProperties(
getProperties().getProperties(), new HibernateSettings().ddlAuto(defaultDdlMode)
.hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers)));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -120,15 +120,15 @@ public abstract class JpaBaseConfiguration {
public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers) {
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, buildJpaProperties(),
persistenceUnitManager.getIfAvailable());
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter,
this::buildJpaProperties, persistenceUnitManager.getIfAvailable());
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}
private Map<String, ?> buildJpaProperties() {
private Map<String, ?> buildJpaProperties(DataSource dataSource) {
Map<String, Object> properties = new HashMap<>(this.properties.getProperties());
Map<String, Object> vendorProperties = getVendorProperties();
Map<String, Object> vendorProperties = getVendorProperties(dataSource);
customizeVendorProperties(vendorProperties);
properties.putAll(vendorProperties);
return properties;
@ -148,7 +148,24 @@ public abstract class JpaBaseConfiguration {
protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();
protected abstract Map<String, Object> getVendorProperties();
/**
* Return the vendor-specific properties for the given {@link DataSource}.
* @param dataSource the data source
* @return the vendor properties
* @since 3.4.4
*/
protected abstract Map<String, Object> getVendorProperties(DataSource dataSource);
/**
* Return the vendor-specific properties.
* @return the vendor properties
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
* {@link #getVendorProperties(DataSource)}
*/
@Deprecated(since = "3.4.4", forRemoval = true)
protected Map<String, Object> getVendorProperties() {
return getVendorProperties(getDataSource());
}
/**
* Customize vendor properties before they are used. Allows for post-processing (for

View File

@ -65,11 +65,14 @@ import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.Fly
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.OracleFlywayConfigurationCustomizer;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.PostgresqlFlywayConfigurationCustomizer;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.SqlServerFlywayConfigurationCustomizer;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.SchemaManagement;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.test.context.FilteredClassLoader;
@ -489,6 +492,36 @@ class FlywayAutoConfigurationTests {
.run((context) -> assertThat(context).hasNotFailed());
}
@Test
@WithMetaInfPersistenceXmlResource
void jpaApplyDdl() {
this.contextRunner
.withConfiguration(
AutoConfigurations.of(DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class))
.run((context) -> {
Map<String, Object> jpaProperties = context.getBean(LocalContainerEntityManagerFactoryBean.class)
.getJpaPropertyMap();
assertThat(jpaProperties).doesNotContainKey("hibernate.hbm2ddl.auto");
});
}
@Test
@WithMetaInfPersistenceXmlResource
void jpaAndMultipleDataSourcesApplyDdl() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class))
.withUserConfiguration(JpaWithMultipleDataSourcesConfiguration.class)
.run((context) -> {
LocalContainerEntityManagerFactoryBean normalEntityManagerFactoryBean = context
.getBean("&normalEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
assertThat(normalEntityManagerFactoryBean.getJpaPropertyMap()).containsEntry("configured", "normal")
.containsEntry("hibernate.hbm2ddl.auto", "create-drop");
LocalContainerEntityManagerFactoryBean flywayEntityManagerFactoryBean = context
.getBean("&flywayEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
assertThat(flywayEntityManagerFactoryBean.getJpaPropertyMap()).containsEntry("configured", "flyway")
.doesNotContainKey("hibernate.hbm2ddl.auto");
});
}
@Test
void customFlywayWithJdbc() {
this.contextRunner
@ -962,6 +995,13 @@ class FlywayAutoConfigurationTests {
};
}
private static Map<String, ?> configureJpaProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "manually");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return properties;
}
@Configuration(proxyBeanMethods = false)
static class FlywayDataSourceConfiguration {
@ -1057,10 +1097,8 @@ class FlywayAutoConfigurationTests {
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "manually");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), properties, null)
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), (ds) -> configureJpaProperties(),
null)
.dataSource(dataSource)
.build();
}
@ -1083,16 +1121,54 @@ class FlywayAutoConfigurationTests {
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "manually");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), properties, null)
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(),
(datasource) -> configureJpaProperties(), null)
.dataSource(this.dataSource)
.build();
}
}
@Configuration(proxyBeanMethods = false)
static class JpaWithMultipleDataSourcesConfiguration {
@Bean
@Primary
DataSource normalDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
.generateUniqueName(true)
.build();
}
@Bean
@Primary
LocalContainerEntityManagerFactoryBean normalEntityManagerFactory(EntityManagerFactoryBuilder builder,
DataSource normalDataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "normal");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return builder.dataSource(normalDataSource).properties(properties).build();
}
@Bean
@FlywayDataSource
DataSource flywayDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
.generateUniqueName(true)
.build();
}
@Bean
LocalContainerEntityManagerFactoryBean flywayEntityManagerFactory(EntityManagerFactoryBuilder builder,
@FlywayDataSource DataSource flywayDataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "flyway");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return builder.dataSource(flywayDataSource).properties(properties).build();
}
}
@Configuration
static class CustomFlywayWithJdbcConfiguration {

View File

@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.liquibase;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -26,6 +27,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
@ -33,6 +35,10 @@ import java.util.function.Consumer;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import liquibase.Liquibase;
import liquibase.UpdateSummaryEnum;
import liquibase.UpdateSummaryOutputEnum;
@ -40,6 +46,7 @@ import liquibase.command.core.helpers.ShowSummaryArgument;
import liquibase.integration.spring.Customizer;
import liquibase.integration.spring.SpringLiquibase;
import liquibase.ui.UIServiceEnum;
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
@ -49,6 +56,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
@ -56,6 +64,8 @@ import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -71,6 +81,7 @@ import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -549,6 +560,38 @@ class LiquibaseAutoConfigurationTests {
});
}
@Test
@WithDbChangelogMasterYamlResource
@WithMetaInfPersistenceXmlResource
void jpaApplyDdl() {
this.contextRunner
.withConfiguration(
AutoConfigurations.of(DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class))
.run((context) -> {
Map<String, Object> jpaProperties = context.getBean(LocalContainerEntityManagerFactoryBean.class)
.getJpaPropertyMap();
assertThat(jpaProperties).doesNotContainKey("hibernate.hbm2ddl.auto");
});
}
@Test
@WithDbChangelogMasterYamlResource
@WithMetaInfPersistenceXmlResource
void jpaAndMultipleDataSourcesApplyDdl() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class))
.withUserConfiguration(JpaWithMultipleDataSourcesConfiguration.class)
.run((context) -> {
LocalContainerEntityManagerFactoryBean normalEntityManagerFactoryBean = context
.getBean("&normalEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
assertThat(normalEntityManagerFactoryBean.getJpaPropertyMap()).containsEntry("configured", "normal")
.containsEntry("hibernate.hbm2ddl.auto", "create-drop");
LocalContainerEntityManagerFactoryBean liquibaseEntityManagerFactory = context
.getBean("&liquibaseEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
assertThat(liquibaseEntityManagerFactory.getJpaPropertyMap()).containsEntry("configured", "liquibase")
.doesNotContainKey("hibernate.hbm2ddl.auto");
});
}
@Test
@WithDbChangelogMasterYamlResource
void userConfigurationJdbcTemplateDependency() {
@ -649,6 +692,46 @@ class LiquibaseAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class JpaWithMultipleDataSourcesConfiguration {
@Bean
@Primary
DataSource normalDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
.generateUniqueName(true)
.build();
}
@Bean
@Primary
LocalContainerEntityManagerFactoryBean normalEntityManagerFactory(EntityManagerFactoryBuilder builder,
DataSource normalDataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "normal");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return builder.dataSource(normalDataSource).properties(properties).build();
}
@Bean
@LiquibaseDataSource
DataSource liquibaseDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
.generateUniqueName(true)
.build();
}
@Bean
LocalContainerEntityManagerFactoryBean liquibaseEntityManagerFactory(EntityManagerFactoryBuilder builder,
@LiquibaseDataSource DataSource liquibaseDataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("configured", "liquibase");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
return builder.dataSource(liquibaseDataSource).properties(properties).build();
}
}
@Configuration(proxyBeanMethods = false)
static class CustomDataSourceConfiguration {
@ -785,4 +868,74 @@ class LiquibaseAutoConfigurationTests {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@WithResource(name = "META-INF/persistence.xml",
content = """
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence https://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="manually-configured">
<class>org.springframework.boot.autoconfigure.flyway.FlywayAutoConfigurationTests$City</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
</persistence>
""")
@interface WithMetaInfPersistenceXmlResource {
}
@Entity
public static class City implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String state;
@Column(nullable = false)
private String country;
@Column(nullable = false)
private String map;
protected City() {
}
City(String name, String state, String country, String map) {
this.name = name;
this.state = state;
this.country = country;
this.map = map;
}
public String getName() {
return this.name;
}
public String getState() {
return this.state;
}
public String getCountry() {
return this.country;
}
public String getMap() {
return this.map;
}
@Override
public String toString() {
return getName() + "," + getState() + "," + getCountry();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -81,7 +81,8 @@ class CustomHibernateJpaAutoConfigurationTests {
.withPropertyValues("spring.datasource.url:jdbc:h2:mem:naming-strategy-beans")
.run((context) -> {
HibernateJpaConfiguration jpaConfiguration = context.getBean(HibernateJpaConfiguration.class);
Map<String, Object> hibernateProperties = jpaConfiguration.getVendorProperties();
Map<String, Object> hibernateProperties = jpaConfiguration
.getVendorProperties(context.getBean(DataSource.class));
assertThat(hibernateProperties).containsEntry("hibernate.implicit_naming_strategy",
NamingStrategyConfiguration.implicitNamingStrategy);
assertThat(hibernateProperties).containsEntry("hibernate.physical_naming_strategy",
@ -93,7 +94,8 @@ class CustomHibernateJpaAutoConfigurationTests {
void hibernatePropertiesCustomizersAreAppliedInOrder() {
this.contextRunner.withUserConfiguration(HibernatePropertiesCustomizerConfiguration.class).run((context) -> {
HibernateJpaConfiguration jpaConfiguration = context.getBean(HibernateJpaConfiguration.class);
Map<String, Object> hibernateProperties = jpaConfiguration.getVendorProperties();
Map<String, Object> hibernateProperties = jpaConfiguration
.getVendorProperties(context.getBean(DataSource.class));
assertThat(hibernateProperties).containsEntry("test.counter", 2);
});
}

View File

@ -75,6 +75,7 @@ import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
@ -399,8 +400,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Test
void physicalNamingStrategyCanBeUsed() {
contextRunner().withUserConfiguration(TestPhysicalNamingStrategyConfiguration.class).run((context) -> {
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
.getVendorProperties();
Map<String, Object> hibernateProperties = getVendorProperties(context);
assertThat(hibernateProperties)
.contains(entry("hibernate.physical_naming_strategy", context.getBean("testPhysicalNamingStrategy")));
assertThat(hibernateProperties).doesNotContainKeys("hibernate.ejb.naming_strategy");
@ -410,8 +410,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Test
void implicitNamingStrategyCanBeUsed() {
contextRunner().withUserConfiguration(TestImplicitNamingStrategyConfiguration.class).run((context) -> {
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
.getVendorProperties();
Map<String, Object> hibernateProperties = getVendorProperties(context);
assertThat(hibernateProperties)
.contains(entry("hibernate.implicit_naming_strategy", context.getBean("testImplicitNamingStrategy")));
assertThat(hibernateProperties).doesNotContainKeys("hibernate.ejb.naming_strategy");
@ -426,8 +425,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
.withPropertyValues("spring.jpa.hibernate.naming.physical-strategy:com.example.Physical",
"spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit")
.run((context) -> {
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
.getVendorProperties();
Map<String, Object> hibernateProperties = getVendorProperties(context);
assertThat(hibernateProperties).contains(
entry("hibernate.physical_naming_strategy", context.getBean("testPhysicalNamingStrategy")),
entry("hibernate.implicit_naming_strategy", context.getBean("testImplicitNamingStrategy")));
@ -443,8 +441,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
.withPropertyValues("spring.jpa.hibernate.naming.physical-strategy:com.example.Physical",
"spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit")
.run((context) -> {
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
.getVendorProperties();
Map<String, Object> hibernateProperties = getVendorProperties(context);
TestHibernatePropertiesCustomizerConfiguration configuration = context
.getBean(TestHibernatePropertiesCustomizerConfiguration.class);
assertThat(hibernateProperties).contains(
@ -536,8 +533,11 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
private ContextConsumer<AssertableApplicationContext> vendorProperties(
Consumer<Map<String, Object>> vendorProperties) {
return (context) -> vendorProperties
.accept(context.getBean(HibernateJpaConfiguration.class).getVendorProperties());
return (context) -> vendorProperties.accept(getVendorProperties(context));
}
private static Map<String, Object> getVendorProperties(ConfigurableApplicationContext context) {
return context.getBean(HibernateJpaConfiguration.class).getVendorProperties(context.getBean(DataSource.class));
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,10 @@
package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
@ -48,7 +52,9 @@ public class MyAdditionalEntityManagerFactoryConfiguration {
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
Function<DataSource, Map<String, ?>> jpaPropertiesFactory = (dataSource) -> createJpaProperties(dataSource,
jpaProperties.getProperties());
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null);
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
@ -56,4 +62,10 @@ public class MyAdditionalEntityManagerFactoryConfiguration {
return new HibernateJpaVendorAdapter();
}
private Map<String, ?> createJpaProperties(DataSource dataSource, Map<String, ?> existingProperties) {
Map<String, ?> jpaProperties = new LinkedHashMap<>(existingProperties);
// ... map JPA properties that require the DataSource (e.g. DDL flags)
return jpaProperties;
}
}

View File

@ -16,8 +16,6 @@
package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers
import javax.sql.DataSource
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
import org.springframework.boot.context.properties.ConfigurationProperties
@ -27,6 +25,7 @@ import org.springframework.context.annotation.Configuration
import org.springframework.orm.jpa.JpaVendorAdapter
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
import javax.sql.DataSource
@Suppress("UNUSED_PARAMETER")
@Configuration(proxyBeanMethods = false)
@ -51,7 +50,9 @@ class MyAdditionalEntityManagerFactoryConfiguration {
private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties)
return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.properties, null)
val jpaPropertiesFactory = { dataSource: DataSource ->
createJpaProperties(dataSource, jpaProperties.properties) }
return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null)
}
private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter {
@ -59,4 +60,10 @@ class MyAdditionalEntityManagerFactoryConfiguration {
return HibernateJpaVendorAdapter()
}
private fun createJpaProperties(dataSource: DataSource, existingProperties: Map<String, *>): Map<String, *> {
val jpaProperties: Map<String, *> = LinkedHashMap(existingProperties)
// ... map JPA properties that require the DataSource (e.g. DDL flags)
return jpaProperties
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.sql.DataSource;
@ -54,7 +55,7 @@ public class EntityManagerFactoryBuilder {
private final PersistenceUnitManager persistenceUnitManager;
private final Map<String, Object> jpaProperties;
private final Function<DataSource, Map<String, ?>> jpaPropertiesFactory;
private final URL persistenceUnitRootLocation;
@ -62,6 +63,42 @@ public class EntityManagerFactoryBuilder {
private PersistenceUnitPostProcessor[] persistenceUnitPostProcessors;
/**
* Create a new instance passing in the common pieces that will be shared if multiple
* EntityManagerFactory instances are created.
* @param jpaVendorAdapter a vendor adapter
* @param jpaPropertiesFactory the JPA properties to be passed to the persistence
* provider, based on the {@linkplain #dataSource(DataSource) configured data source}
* @param persistenceUnitManager optional source of persistence unit information (can
* be null)
* @since 3.4.4
*/
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
Function<DataSource, Map<String, ?>> jpaPropertiesFactory, PersistenceUnitManager persistenceUnitManager) {
this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, null);
}
/**
* Create a new instance passing in the common pieces that will be shared if multiple
* EntityManagerFactory instances are created.
* @param jpaVendorAdapter a vendor adapter
* @param jpaPropertiesFactory the JPA properties to be passed to the persistence
* provider, based on the {@linkplain #dataSource(DataSource) configured data source}
* @param persistenceUnitManager optional source of persistence unit information (can
* be null)
* @param persistenceUnitRootLocation the persistence unit root location to use as a
* fallback or {@code null}
* @since 3.4.4
*/
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
Function<DataSource, Map<String, ?>> jpaPropertiesFactory, PersistenceUnitManager persistenceUnitManager,
URL persistenceUnitRootLocation) {
this.jpaVendorAdapter = jpaVendorAdapter;
this.persistenceUnitManager = persistenceUnitManager;
this.jpaPropertiesFactory = jpaPropertiesFactory;
this.persistenceUnitRootLocation = persistenceUnitRootLocation;
}
/**
* Create a new instance passing in the common pieces that will be shared if multiple
* EntityManagerFactory instances are created.
@ -69,10 +106,13 @@ public class EntityManagerFactoryBuilder {
* @param jpaProperties the JPA properties to be passed to the persistence provider
* @param persistenceUnitManager optional source of persistence unit information (can
* be null)
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
* {@link #EntityManagerFactoryBuilder(JpaVendorAdapter, Function, PersistenceUnitManager)}
*/
@Deprecated(since = "3.4.4", forRemoval = true)
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String, ?> jpaProperties,
PersistenceUnitManager persistenceUnitManager) {
this(jpaVendorAdapter, jpaProperties, persistenceUnitManager, null);
this(jpaVendorAdapter, (datasource) -> jpaProperties, persistenceUnitManager, null);
}
/**
@ -85,15 +125,21 @@ public class EntityManagerFactoryBuilder {
* @param persistenceUnitRootLocation the persistence unit root location to use as a
* fallback or {@code null}
* @since 1.4.1
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
* {@link #EntityManagerFactoryBuilder(JpaVendorAdapter, Function, PersistenceUnitManager, URL)}
*/
@Deprecated(since = "3.4.4", forRemoval = true)
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String, ?> jpaProperties,
PersistenceUnitManager persistenceUnitManager, URL persistenceUnitRootLocation) {
this.jpaVendorAdapter = jpaVendorAdapter;
this.persistenceUnitManager = persistenceUnitManager;
this.jpaProperties = new LinkedHashMap<>(jpaProperties);
this.persistenceUnitRootLocation = persistenceUnitRootLocation;
this(jpaVendorAdapter, (datasource) -> jpaProperties, persistenceUnitManager, persistenceUnitRootLocation);
}
/**
* Create a new {@link Builder} for a {@code EntityManagerFactory} using the settings
* of the given instance, and the given {@link DataSource}.
* @param dataSource the data source to use
* @return a builder to create an {@code EntityManagerFactory}
*/
public Builder dataSource(DataSource dataSource) {
return new Builder(dataSource);
}
@ -255,7 +301,8 @@ public class EntityManagerFactoryBuilder {
else {
entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
}
entityManagerFactoryBean.getJpaPropertyMap().putAll(EntityManagerFactoryBuilder.this.jpaProperties);
Map<String, ?> jpaProperties = EntityManagerFactoryBuilder.this.jpaPropertiesFactory.apply(this.dataSource);
entityManagerFactoryBean.getJpaPropertyMap().putAll(new LinkedHashMap<>(jpaProperties));
entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
if (!ObjectUtils.isEmpty(this.mappingResources)) {
entityManagerFactoryBean.setMappingResources(this.mappingResources);