Skip to content

Commit e06244d

Browse files
committedMar 6, 2025·
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
1 parent d097870 commit e06244d

File tree

10 files changed

+361
-46
lines changed

10 files changed

+361
-46
lines changed
 

‎spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa;
1818

19-
import java.util.HashMap;
2019
import java.util.Map;
20+
import java.util.function.Function;
2121

2222
import javax.sql.DataSource;
2323

@@ -194,9 +194,10 @@ LocalContainerEntityManagerFactoryBean nonAutowire(DataSource ds) {
194194
}
195195

196196
private LocalContainerEntityManagerFactoryBean createSessionFactory(DataSource ds) {
197-
Map<String, String> jpaProperties = new HashMap<>();
198-
jpaProperties.put("hibernate.generate_statistics", "true");
199-
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaProperties, null).dataSource(ds)
197+
Function<DataSource, Map<String, ?>> jpaPropertiesFactory = (dataSource) -> Map
198+
.of("hibernate.generate_statistics", "true");
199+
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaPropertiesFactory, null)
200+
.dataSource(ds)
200201
.packages(PACKAGE_CLASSES)
201202
.build();
202203
}

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -138,8 +138,8 @@ protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
138138
}
139139

140140
@Override
141-
protected Map<String, Object> getVendorProperties() {
142-
Supplier<String> defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(getDataSource());
141+
protected Map<String, Object> getVendorProperties(DataSource dataSource) {
142+
Supplier<String> defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(dataSource);
143143
return new LinkedHashMap<>(this.hibernateProperties.determineHibernateProperties(
144144
getProperties().getProperties(), new HibernateSettings().ddlAuto(defaultDdlMode)
145145
.hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers)));

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java

+23-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -120,15 +120,15 @@ public JpaVendorAdapter jpaVendorAdapter() {
120120
public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
121121
ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
122122
ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers) {
123-
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, buildJpaProperties(),
124-
persistenceUnitManager.getIfAvailable());
123+
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter,
124+
this::buildJpaProperties, persistenceUnitManager.getIfAvailable());
125125
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
126126
return builder;
127127
}
128128

129-
private Map<String, ?> buildJpaProperties() {
129+
private Map<String, ?> buildJpaProperties(DataSource dataSource) {
130130
Map<String, Object> properties = new HashMap<>(this.properties.getProperties());
131-
Map<String, Object> vendorProperties = getVendorProperties();
131+
Map<String, Object> vendorProperties = getVendorProperties(dataSource);
132132
customizeVendorProperties(vendorProperties);
133133
properties.putAll(vendorProperties);
134134
return properties;
@@ -148,7 +148,24 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManager
148148

149149
protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();
150150

151-
protected abstract Map<String, Object> getVendorProperties();
151+
/**
152+
* Return the vendor-specific properties for the given {@link DataSource}.
153+
* @param dataSource the data source
154+
* @return the vendor properties
155+
* @since 3.4.4
156+
*/
157+
protected abstract Map<String, Object> getVendorProperties(DataSource dataSource);
158+
159+
/**
160+
* Return the vendor-specific properties.
161+
* @return the vendor properties
162+
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
163+
* {@link #getVendorProperties(DataSource)}
164+
*/
165+
@Deprecated(since = "3.4.4", forRemoval = true)
166+
protected Map<String, Object> getVendorProperties() {
167+
return getVendorProperties(getDataSource());
168+
}
152169

153170
/**
154171
* Customize vendor properties before they are used. Allows for post-processing (for

‎spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java

+83-7
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@
6565
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.OracleFlywayConfigurationCustomizer;
6666
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.PostgresqlFlywayConfigurationCustomizer;
6767
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.SqlServerFlywayConfigurationCustomizer;
68+
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
6869
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
6970
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
7071
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
72+
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
7173
import org.springframework.boot.context.properties.EnableConfigurationProperties;
7274
import org.springframework.boot.jdbc.DataSourceBuilder;
75+
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
7376
import org.springframework.boot.jdbc.SchemaManagement;
7477
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
7578
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -489,6 +492,36 @@ void customFlywayWithJpa() {
489492
.run((context) -> assertThat(context).hasNotFailed());
490493
}
491494

495+
@Test
496+
@WithMetaInfPersistenceXmlResource
497+
void jpaApplyDdl() {
498+
this.contextRunner
499+
.withConfiguration(
500+
AutoConfigurations.of(DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class))
501+
.run((context) -> {
502+
Map<String, Object> jpaProperties = context.getBean(LocalContainerEntityManagerFactoryBean.class)
503+
.getJpaPropertyMap();
504+
assertThat(jpaProperties).doesNotContainKey("hibernate.hbm2ddl.auto");
505+
});
506+
}
507+
508+
@Test
509+
@WithMetaInfPersistenceXmlResource
510+
void jpaAndMultipleDataSourcesApplyDdl() {
511+
this.contextRunner.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class))
512+
.withUserConfiguration(JpaWithMultipleDataSourcesConfiguration.class)
513+
.run((context) -> {
514+
LocalContainerEntityManagerFactoryBean normalEntityManagerFactoryBean = context
515+
.getBean("&normalEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
516+
assertThat(normalEntityManagerFactoryBean.getJpaPropertyMap()).containsEntry("configured", "normal")
517+
.containsEntry("hibernate.hbm2ddl.auto", "create-drop");
518+
LocalContainerEntityManagerFactoryBean flywayEntityManagerFactoryBean = context
519+
.getBean("&flywayEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
520+
assertThat(flywayEntityManagerFactoryBean.getJpaPropertyMap()).containsEntry("configured", "flyway")
521+
.doesNotContainKey("hibernate.hbm2ddl.auto");
522+
});
523+
}
524+
492525
@Test
493526
void customFlywayWithJdbc() {
494527
this.contextRunner
@@ -962,6 +995,13 @@ private ContextConsumer<AssertableApplicationContext> validateFlywayTeamsPropert
962995
};
963996
}
964997

998+
private static Map<String, ?> configureJpaProperties() {
999+
Map<String, Object> properties = new HashMap<>();
1000+
properties.put("configured", "manually");
1001+
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
1002+
return properties;
1003+
}
1004+
9651005
@Configuration(proxyBeanMethods = false)
9661006
static class FlywayDataSourceConfiguration {
9671007

@@ -1057,10 +1097,8 @@ FlywayMigrationInitializer customFlywayMigrationInitializer(Flyway flyway) {
10571097

10581098
@Bean
10591099
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource) {
1060-
Map<String, Object> properties = new HashMap<>();
1061-
properties.put("configured", "manually");
1062-
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
1063-
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), properties, null)
1100+
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), (ds) -> configureJpaProperties(),
1101+
null)
10641102
.dataSource(dataSource)
10651103
.build();
10661104
}
@@ -1083,14 +1121,52 @@ Flyway customFlyway() {
10831121

10841122
@Bean
10851123
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
1124+
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(),
1125+
(datasource) -> configureJpaProperties(), null)
1126+
.dataSource(this.dataSource)
1127+
.build();
1128+
}
1129+
1130+
}
1131+
1132+
@Configuration(proxyBeanMethods = false)
1133+
static class JpaWithMultipleDataSourcesConfiguration {
1134+
1135+
@Bean
1136+
@Primary
1137+
DataSource normalDataSource() {
1138+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
1139+
.generateUniqueName(true)
1140+
.build();
1141+
}
1142+
1143+
@Bean
1144+
@Primary
1145+
LocalContainerEntityManagerFactoryBean normalEntityManagerFactory(EntityManagerFactoryBuilder builder,
1146+
DataSource normalDataSource) {
10861147
Map<String, Object> properties = new HashMap<>();
1087-
properties.put("configured", "manually");
1148+
properties.put("configured", "normal");
10881149
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
1089-
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), properties, null)
1090-
.dataSource(this.dataSource)
1150+
return builder.dataSource(normalDataSource).properties(properties).build();
1151+
}
1152+
1153+
@Bean
1154+
@FlywayDataSource
1155+
DataSource flywayDataSource() {
1156+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
1157+
.generateUniqueName(true)
10911158
.build();
10921159
}
10931160

1161+
@Bean
1162+
LocalContainerEntityManagerFactoryBean flywayEntityManagerFactory(EntityManagerFactoryBuilder builder,
1163+
@FlywayDataSource DataSource flywayDataSource) {
1164+
Map<String, Object> properties = new HashMap<>();
1165+
properties.put("configured", "flyway");
1166+
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
1167+
return builder.dataSource(flywayDataSource).properties(properties).build();
1168+
}
1169+
10941170
}
10951171

10961172
@Configuration

‎spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java

+153
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.io.Serializable;
2122
import java.lang.annotation.ElementType;
2223
import java.lang.annotation.Retention;
2324
import java.lang.annotation.RetentionPolicy;
@@ -26,20 +27,26 @@
2627
import java.nio.file.Path;
2728
import java.sql.Connection;
2829
import java.sql.SQLException;
30+
import java.util.HashMap;
2931
import java.util.Map;
3032
import java.util.UUID;
3133
import java.util.function.Consumer;
3234

3335
import javax.sql.DataSource;
3436

3537
import com.zaxxer.hikari.HikariDataSource;
38+
import jakarta.persistence.Column;
39+
import jakarta.persistence.Entity;
40+
import jakarta.persistence.GeneratedValue;
41+
import jakarta.persistence.Id;
3642
import liquibase.Liquibase;
3743
import liquibase.UpdateSummaryEnum;
3844
import liquibase.UpdateSummaryOutputEnum;
3945
import liquibase.command.core.helpers.ShowSummaryArgument;
4046
import liquibase.integration.spring.Customizer;
4147
import liquibase.integration.spring.SpringLiquibase;
4248
import liquibase.ui.UIServiceEnum;
49+
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
4350
import org.junit.jupiter.api.Test;
4451
import org.junit.jupiter.api.extension.ExtendWith;
4552
import org.junit.jupiter.api.io.TempDir;
@@ -49,13 +56,16 @@
4956
import org.springframework.beans.factory.BeanCreationException;
5057
import org.springframework.beans.factory.config.BeanDefinition;
5158
import org.springframework.boot.autoconfigure.AutoConfigurations;
59+
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
5260
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
5361
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
5462
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
5563
import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
5664
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
5765
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
5866
import org.springframework.boot.jdbc.DataSourceBuilder;
67+
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
68+
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
5969
import org.springframework.boot.test.context.FilteredClassLoader;
6070
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
6171
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -71,6 +81,7 @@
7181
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
7282
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
7383
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
84+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
7485
import org.springframework.test.util.ReflectionTestUtils;
7586

7687
import static org.assertj.core.api.Assertions.assertThat;
@@ -549,6 +560,38 @@ void userConfigurationEntityManagerFactoryDependency() {
549560
});
550561
}
551562

563+
@Test
564+
@WithDbChangelogMasterYamlResource
565+
@WithMetaInfPersistenceXmlResource
566+
void jpaApplyDdl() {
567+
this.contextRunner
568+
.withConfiguration(
569+
AutoConfigurations.of(DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class))
570+
.run((context) -> {
571+
Map<String, Object> jpaProperties = context.getBean(LocalContainerEntityManagerFactoryBean.class)
572+
.getJpaPropertyMap();
573+
assertThat(jpaProperties).doesNotContainKey("hibernate.hbm2ddl.auto");
574+
});
575+
}
576+
577+
@Test
578+
@WithDbChangelogMasterYamlResource
579+
@WithMetaInfPersistenceXmlResource
580+
void jpaAndMultipleDataSourcesApplyDdl() {
581+
this.contextRunner.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class))
582+
.withUserConfiguration(JpaWithMultipleDataSourcesConfiguration.class)
583+
.run((context) -> {
584+
LocalContainerEntityManagerFactoryBean normalEntityManagerFactoryBean = context
585+
.getBean("&normalEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
586+
assertThat(normalEntityManagerFactoryBean.getJpaPropertyMap()).containsEntry("configured", "normal")
587+
.containsEntry("hibernate.hbm2ddl.auto", "create-drop");
588+
LocalContainerEntityManagerFactoryBean liquibaseEntityManagerFactory = context
589+
.getBean("&liquibaseEntityManagerFactory", LocalContainerEntityManagerFactoryBean.class);
590+
assertThat(liquibaseEntityManagerFactory.getJpaPropertyMap()).containsEntry("configured", "liquibase")
591+
.doesNotContainKey("hibernate.hbm2ddl.auto");
592+
});
593+
}
594+
552595
@Test
553596
@WithDbChangelogMasterYamlResource
554597
void userConfigurationJdbcTemplateDependency() {
@@ -649,6 +692,46 @@ SpringLiquibase springLiquibase(DataSource dataSource) {
649692

650693
}
651694

695+
@Configuration(proxyBeanMethods = false)
696+
static class JpaWithMultipleDataSourcesConfiguration {
697+
698+
@Bean
699+
@Primary
700+
DataSource normalDataSource() {
701+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
702+
.generateUniqueName(true)
703+
.build();
704+
}
705+
706+
@Bean
707+
@Primary
708+
LocalContainerEntityManagerFactoryBean normalEntityManagerFactory(EntityManagerFactoryBuilder builder,
709+
DataSource normalDataSource) {
710+
Map<String, Object> properties = new HashMap<>();
711+
properties.put("configured", "normal");
712+
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
713+
return builder.dataSource(normalDataSource).properties(properties).build();
714+
}
715+
716+
@Bean
717+
@LiquibaseDataSource
718+
DataSource liquibaseDataSource() {
719+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.HSQLDB.getType())
720+
.generateUniqueName(true)
721+
.build();
722+
}
723+
724+
@Bean
725+
LocalContainerEntityManagerFactoryBean liquibaseEntityManagerFactory(EntityManagerFactoryBuilder builder,
726+
@LiquibaseDataSource DataSource liquibaseDataSource) {
727+
Map<String, Object> properties = new HashMap<>();
728+
properties.put("configured", "liquibase");
729+
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
730+
return builder.dataSource(liquibaseDataSource).properties(properties).build();
731+
}
732+
733+
}
734+
652735
@Configuration(proxyBeanMethods = false)
653736
static class CustomDataSourceConfiguration {
654737

@@ -785,4 +868,74 @@ static class CustomH2Driver extends org.h2.Driver {
785868

786869
}
787870

871+
@Target(ElementType.METHOD)
872+
@Retention(RetentionPolicy.RUNTIME)
873+
@WithResource(name = "META-INF/persistence.xml",
874+
content = """
875+
<?xml version="1.0" encoding="UTF-8"?>
876+
<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">
877+
<persistence-unit name="manually-configured">
878+
<class>org.springframework.boot.autoconfigure.flyway.FlywayAutoConfigurationTests$City</class>
879+
<exclude-unlisted-classes>true</exclude-unlisted-classes>
880+
</persistence-unit>
881+
</persistence>
882+
""")
883+
@interface WithMetaInfPersistenceXmlResource {
884+
885+
}
886+
887+
@Entity
888+
public static class City implements Serializable {
889+
890+
private static final long serialVersionUID = 1L;
891+
892+
@Id
893+
@GeneratedValue
894+
private Long id;
895+
896+
@Column(nullable = false)
897+
private String name;
898+
899+
@Column(nullable = false)
900+
private String state;
901+
902+
@Column(nullable = false)
903+
private String country;
904+
905+
@Column(nullable = false)
906+
private String map;
907+
908+
protected City() {
909+
}
910+
911+
City(String name, String state, String country, String map) {
912+
this.name = name;
913+
this.state = state;
914+
this.country = country;
915+
this.map = map;
916+
}
917+
918+
public String getName() {
919+
return this.name;
920+
}
921+
922+
public String getState() {
923+
return this.state;
924+
}
925+
926+
public String getCountry() {
927+
return this.country;
928+
}
929+
930+
public String getMap() {
931+
return this.map;
932+
}
933+
934+
@Override
935+
public String toString() {
936+
return getName() + "," + getState() + "," + getCountry();
937+
}
938+
939+
}
940+
788941
}

‎spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,7 +81,8 @@ void namingStrategyBeansAreUsed() {
8181
.withPropertyValues("spring.datasource.url:jdbc:h2:mem:naming-strategy-beans")
8282
.run((context) -> {
8383
HibernateJpaConfiguration jpaConfiguration = context.getBean(HibernateJpaConfiguration.class);
84-
Map<String, Object> hibernateProperties = jpaConfiguration.getVendorProperties();
84+
Map<String, Object> hibernateProperties = jpaConfiguration
85+
.getVendorProperties(context.getBean(DataSource.class));
8586
assertThat(hibernateProperties).containsEntry("hibernate.implicit_naming_strategy",
8687
NamingStrategyConfiguration.implicitNamingStrategy);
8788
assertThat(hibernateProperties).containsEntry("hibernate.physical_naming_strategy",
@@ -93,7 +94,8 @@ void namingStrategyBeansAreUsed() {
9394
void hibernatePropertiesCustomizersAreAppliedInOrder() {
9495
this.contextRunner.withUserConfiguration(HibernatePropertiesCustomizerConfiguration.class).run((context) -> {
9596
HibernateJpaConfiguration jpaConfiguration = context.getBean(HibernateJpaConfiguration.class);
96-
Map<String, Object> hibernateProperties = jpaConfiguration.getVendorProperties();
97+
Map<String, Object> hibernateProperties = jpaConfiguration
98+
.getVendorProperties(context.getBean(DataSource.class));
9799
assertThat(hibernateProperties).containsEntry("test.counter", 2);
98100
});
99101
}

‎spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.springframework.boot.testsupport.classpath.resources.WithResource;
7676
import org.springframework.context.ApplicationEvent;
7777
import org.springframework.context.ApplicationListener;
78+
import org.springframework.context.ConfigurableApplicationContext;
7879
import org.springframework.context.annotation.Bean;
7980
import org.springframework.context.annotation.Configuration;
8081
import org.springframework.context.event.ContextRefreshedEvent;
@@ -399,8 +400,7 @@ void customResourceMapping() {
399400
@Test
400401
void physicalNamingStrategyCanBeUsed() {
401402
contextRunner().withUserConfiguration(TestPhysicalNamingStrategyConfiguration.class).run((context) -> {
402-
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
403-
.getVendorProperties();
403+
Map<String, Object> hibernateProperties = getVendorProperties(context);
404404
assertThat(hibernateProperties)
405405
.contains(entry("hibernate.physical_naming_strategy", context.getBean("testPhysicalNamingStrategy")));
406406
assertThat(hibernateProperties).doesNotContainKeys("hibernate.ejb.naming_strategy");
@@ -410,8 +410,7 @@ void physicalNamingStrategyCanBeUsed() {
410410
@Test
411411
void implicitNamingStrategyCanBeUsed() {
412412
contextRunner().withUserConfiguration(TestImplicitNamingStrategyConfiguration.class).run((context) -> {
413-
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
414-
.getVendorProperties();
413+
Map<String, Object> hibernateProperties = getVendorProperties(context);
415414
assertThat(hibernateProperties)
416415
.contains(entry("hibernate.implicit_naming_strategy", context.getBean("testImplicitNamingStrategy")));
417416
assertThat(hibernateProperties).doesNotContainKeys("hibernate.ejb.naming_strategy");
@@ -426,8 +425,7 @@ void namingStrategyInstancesTakePrecedenceOverNamingStrategyProperties() {
426425
.withPropertyValues("spring.jpa.hibernate.naming.physical-strategy:com.example.Physical",
427426
"spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit")
428427
.run((context) -> {
429-
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
430-
.getVendorProperties();
428+
Map<String, Object> hibernateProperties = getVendorProperties(context);
431429
assertThat(hibernateProperties).contains(
432430
entry("hibernate.physical_naming_strategy", context.getBean("testPhysicalNamingStrategy")),
433431
entry("hibernate.implicit_naming_strategy", context.getBean("testImplicitNamingStrategy")));
@@ -443,8 +441,7 @@ void hibernatePropertiesCustomizerTakesPrecedenceOverStrategyInstancesAndNamingS
443441
.withPropertyValues("spring.jpa.hibernate.naming.physical-strategy:com.example.Physical",
444442
"spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit")
445443
.run((context) -> {
446-
Map<String, Object> hibernateProperties = context.getBean(HibernateJpaConfiguration.class)
447-
.getVendorProperties();
444+
Map<String, Object> hibernateProperties = getVendorProperties(context);
448445
TestHibernatePropertiesCustomizerConfiguration configuration = context
449446
.getBean(TestHibernatePropertiesCustomizerConfiguration.class);
450447
assertThat(hibernateProperties).contains(
@@ -536,8 +533,11 @@ void vendorPropertiesWhenBothDdlAutoPropertiesAreSet() {
536533

537534
private ContextConsumer<AssertableApplicationContext> vendorProperties(
538535
Consumer<Map<String, Object>> vendorProperties) {
539-
return (context) -> vendorProperties
540-
.accept(context.getBean(HibernateJpaConfiguration.class).getVendorProperties());
536+
return (context) -> vendorProperties.accept(getVendorProperties(context));
537+
}
538+
539+
private static Map<String, Object> getVendorProperties(ConfigurableApplicationContext context) {
540+
return context.getBean(HibernateJpaConfiguration.class).getVendorProperties(context.getBean(DataSource.class));
541541
}
542542

543543
@Test

‎spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers;
1818

19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
import java.util.function.Function;
22+
1923
import javax.sql.DataSource;
2024

2125
import org.springframework.beans.factory.annotation.Qualifier;
@@ -48,12 +52,20 @@ public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(@Qualif
4852

4953
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
5054
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
51-
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
55+
Function<DataSource, Map<String, ?>> jpaPropertiesFactory = (dataSource) -> createJpaProperties(dataSource,
56+
jpaProperties.getProperties());
57+
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null);
5258
}
5359

5460
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
5561
// ... map JPA properties as needed
5662
return new HibernateJpaVendorAdapter();
5763
}
5864

65+
private Map<String, ?> createJpaProperties(DataSource dataSource, Map<String, ?> existingProperties) {
66+
Map<String, ?> jpaProperties = new LinkedHashMap<>(existingProperties);
67+
// ... map JPA properties that require the DataSource (e.g. DDL flags)
68+
return jpaProperties;
69+
}
70+
5971
}

‎spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt

+10-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers
1818

19-
import javax.sql.DataSource
20-
2119
import org.springframework.beans.factory.annotation.Qualifier
2220
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
2321
import org.springframework.boot.context.properties.ConfigurationProperties
@@ -27,6 +25,7 @@ import org.springframework.context.annotation.Configuration
2725
import org.springframework.orm.jpa.JpaVendorAdapter
2826
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
2927
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
28+
import javax.sql.DataSource
3029

3130
@Suppress("UNUSED_PARAMETER")
3231
@Configuration(proxyBeanMethods = false)
@@ -51,12 +50,20 @@ class MyAdditionalEntityManagerFactoryConfiguration {
5150

5251
private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
5352
val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties)
54-
return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.properties, null)
53+
val jpaPropertiesFactory = { dataSource: DataSource ->
54+
createJpaProperties(dataSource, jpaProperties.properties) }
55+
return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null)
5556
}
5657

5758
private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter {
5859
// ... map JPA properties as needed
5960
return HibernateJpaVendorAdapter()
6061
}
6162

63+
private fun createJpaProperties(dataSource: DataSource, existingProperties: Map<String, *>): Map<String, *> {
64+
val jpaProperties: Map<String, *> = LinkedHashMap(existingProperties)
65+
// ... map JPA properties that require the DataSource (e.g. DDL flags)
66+
return jpaProperties
67+
}
68+
6269
}

‎spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityManagerFactoryBuilder.java

+55-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.LinkedHashMap;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.function.Function;
2526

2627
import javax.sql.DataSource;
2728

@@ -54,25 +55,64 @@ public class EntityManagerFactoryBuilder {
5455

5556
private final PersistenceUnitManager persistenceUnitManager;
5657

57-
private final Map<String, Object> jpaProperties;
58+
private final Function<DataSource, Map<String, ?>> jpaPropertiesFactory;
5859

5960
private final URL persistenceUnitRootLocation;
6061

6162
private AsyncTaskExecutor bootstrapExecutor;
6263

6364
private PersistenceUnitPostProcessor[] persistenceUnitPostProcessors;
6465

66+
/**
67+
* Create a new instance passing in the common pieces that will be shared if multiple
68+
* EntityManagerFactory instances are created.
69+
* @param jpaVendorAdapter a vendor adapter
70+
* @param jpaPropertiesFactory the JPA properties to be passed to the persistence
71+
* provider, based on the {@linkplain #dataSource(DataSource) configured data source}
72+
* @param persistenceUnitManager optional source of persistence unit information (can
73+
* be null)
74+
* @since 3.4.4
75+
*/
76+
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
77+
Function<DataSource, Map<String, ?>> jpaPropertiesFactory, PersistenceUnitManager persistenceUnitManager) {
78+
this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, null);
79+
}
80+
81+
/**
82+
* Create a new instance passing in the common pieces that will be shared if multiple
83+
* EntityManagerFactory instances are created.
84+
* @param jpaVendorAdapter a vendor adapter
85+
* @param jpaPropertiesFactory the JPA properties to be passed to the persistence
86+
* provider, based on the {@linkplain #dataSource(DataSource) configured data source}
87+
* @param persistenceUnitManager optional source of persistence unit information (can
88+
* be null)
89+
* @param persistenceUnitRootLocation the persistence unit root location to use as a
90+
* fallback or {@code null}
91+
* @since 3.4.4
92+
*/
93+
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
94+
Function<DataSource, Map<String, ?>> jpaPropertiesFactory, PersistenceUnitManager persistenceUnitManager,
95+
URL persistenceUnitRootLocation) {
96+
this.jpaVendorAdapter = jpaVendorAdapter;
97+
this.persistenceUnitManager = persistenceUnitManager;
98+
this.jpaPropertiesFactory = jpaPropertiesFactory;
99+
this.persistenceUnitRootLocation = persistenceUnitRootLocation;
100+
}
101+
65102
/**
66103
* Create a new instance passing in the common pieces that will be shared if multiple
67104
* EntityManagerFactory instances are created.
68105
* @param jpaVendorAdapter a vendor adapter
69106
* @param jpaProperties the JPA properties to be passed to the persistence provider
70107
* @param persistenceUnitManager optional source of persistence unit information (can
71108
* be null)
109+
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
110+
* {@link #EntityManagerFactoryBuilder(JpaVendorAdapter, Function, PersistenceUnitManager)}
72111
*/
112+
@Deprecated(since = "3.4.4", forRemoval = true)
73113
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String, ?> jpaProperties,
74114
PersistenceUnitManager persistenceUnitManager) {
75-
this(jpaVendorAdapter, jpaProperties, persistenceUnitManager, null);
115+
this(jpaVendorAdapter, (datasource) -> jpaProperties, persistenceUnitManager, null);
76116
}
77117

78118
/**
@@ -85,15 +125,21 @@ public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String
85125
* @param persistenceUnitRootLocation the persistence unit root location to use as a
86126
* fallback or {@code null}
87127
* @since 1.4.1
128+
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
129+
* {@link #EntityManagerFactoryBuilder(JpaVendorAdapter, Function, PersistenceUnitManager, URL)}
88130
*/
131+
@Deprecated(since = "3.4.4", forRemoval = true)
89132
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String, ?> jpaProperties,
90133
PersistenceUnitManager persistenceUnitManager, URL persistenceUnitRootLocation) {
91-
this.jpaVendorAdapter = jpaVendorAdapter;
92-
this.persistenceUnitManager = persistenceUnitManager;
93-
this.jpaProperties = new LinkedHashMap<>(jpaProperties);
94-
this.persistenceUnitRootLocation = persistenceUnitRootLocation;
134+
this(jpaVendorAdapter, (datasource) -> jpaProperties, persistenceUnitManager, persistenceUnitRootLocation);
95135
}
96136

137+
/**
138+
* Create a new {@link Builder} for a {@code EntityManagerFactory} using the settings
139+
* of the given instance, and the given {@link DataSource}.
140+
* @param dataSource the data source to use
141+
* @return a builder to create an {@code EntityManagerFactory}
142+
*/
97143
public Builder dataSource(DataSource dataSource) {
98144
return new Builder(dataSource);
99145
}
@@ -255,7 +301,8 @@ public LocalContainerEntityManagerFactoryBean build() {
255301
else {
256302
entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
257303
}
258-
entityManagerFactoryBean.getJpaPropertyMap().putAll(EntityManagerFactoryBuilder.this.jpaProperties);
304+
Map<String, ?> jpaProperties = EntityManagerFactoryBuilder.this.jpaPropertiesFactory.apply(this.dataSource);
305+
entityManagerFactoryBean.getJpaPropertyMap().putAll(new LinkedHashMap<>(jpaProperties));
259306
entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
260307
if (!ObjectUtils.isEmpty(this.mappingResources)) {
261308
entityManagerFactoryBean.setMappingResources(this.mappingResources);

0 commit comments

Comments
 (0)
Please sign in to comment.