diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java index 651cfead8b0..fff5efec038 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java @@ -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 jpaProperties = new HashMap<>(); - jpaProperties.put("hibernate.generate_statistics", "true"); - return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaProperties, null).dataSource(ds) + Function> jpaPropertiesFactory = (dataSource) -> Map + .of("hibernate.generate_statistics", "true"); + return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaPropertiesFactory, null) + .dataSource(ds) .packages(PACKAGE_CLASSES) .build(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java index 981f5dd5b0e..6ab852375f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java @@ -145,8 +145,8 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { } @Override - protected Map getVendorProperties() { - Supplier defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(getDataSource()); + protected Map getVendorProperties(DataSource dataSource) { + Supplier defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(dataSource); return new LinkedHashMap<>(this.hibernateProperties.determineHibernateProperties( getProperties().getProperties(), new HibernateSettings().ddlAuto(defaultDdlMode) .hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers))); 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 d6dbc7d287c..2e6ab356f78 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 @@ -120,15 +120,15 @@ public abstract class JpaBaseConfiguration { public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, ObjectProvider persistenceUnitManager, ObjectProvider 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 buildJpaProperties() { + private Map buildJpaProperties(DataSource dataSource) { Map properties = new HashMap<>(this.properties.getProperties()); - Map vendorProperties = getVendorProperties(); + Map 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 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 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 getVendorProperties() { + return getVendorProperties(getDataSource()); + } /** * Customize vendor properties before they are used. Allows for post-processing (for diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index f9e049d8dfd..c695b70a98d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -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 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 configureJpaProperties() { + Map 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 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 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 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 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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index 8b5ee3b40d7..210bb87005f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -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 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() { @@ -669,6 +712,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 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 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 { @@ -805,4 +888,74 @@ class LiquibaseAutoConfigurationTests { } + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @WithResource(name = "META-INF/persistence.xml", + content = """ + + + + org.springframework.boot.autoconfigure.flyway.FlywayAutoConfigurationTests$City + true + + + """) + @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(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java index 7d5fe8cdd7f..64c538416a6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java @@ -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 hibernateProperties = jpaConfiguration.getVendorProperties(); + Map 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 hibernateProperties = jpaConfiguration.getVendorProperties(); + Map hibernateProperties = jpaConfiguration + .getVendorProperties(context.getBean(DataSource.class)); assertThat(hibernateProperties).containsEntry("test.counter", 2); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 35d59fde03f..d64246044c3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -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; @@ -419,8 +420,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes @Test void physicalNamingStrategyCanBeUsed() { contextRunner().withUserConfiguration(TestPhysicalNamingStrategyConfiguration.class).run((context) -> { - Map hibernateProperties = context.getBean(HibernateJpaConfiguration.class) - .getVendorProperties(); + Map hibernateProperties = getVendorProperties(context); assertThat(hibernateProperties) .contains(entry("hibernate.physical_naming_strategy", context.getBean("testPhysicalNamingStrategy"))); assertThat(hibernateProperties).doesNotContainKeys("hibernate.ejb.naming_strategy"); @@ -430,8 +430,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes @Test void implicitNamingStrategyCanBeUsed() { contextRunner().withUserConfiguration(TestImplicitNamingStrategyConfiguration.class).run((context) -> { - Map hibernateProperties = context.getBean(HibernateJpaConfiguration.class) - .getVendorProperties(); + Map hibernateProperties = getVendorProperties(context); assertThat(hibernateProperties) .contains(entry("hibernate.implicit_naming_strategy", context.getBean("testImplicitNamingStrategy"))); assertThat(hibernateProperties).doesNotContainKeys("hibernate.ejb.naming_strategy"); @@ -446,8 +445,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 hibernateProperties = context.getBean(HibernateJpaConfiguration.class) - .getVendorProperties(); + Map hibernateProperties = getVendorProperties(context); assertThat(hibernateProperties).contains( entry("hibernate.physical_naming_strategy", context.getBean("testPhysicalNamingStrategy")), entry("hibernate.implicit_naming_strategy", context.getBean("testImplicitNamingStrategy"))); @@ -463,8 +461,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 hibernateProperties = context.getBean(HibernateJpaConfiguration.class) - .getVendorProperties(); + Map hibernateProperties = getVendorProperties(context); TestHibernatePropertiesCustomizerConfiguration configuration = context .getBean(TestHibernatePropertiesCustomizerConfiguration.class); assertThat(hibernateProperties).contains( @@ -556,8 +553,11 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes private ContextConsumer vendorProperties( Consumer> vendorProperties) { - return (context) -> vendorProperties - .accept(context.getBean(HibernateJpaConfiguration.class).getVendorProperties()); + return (context) -> vendorProperties.accept(getVendorProperties(context)); + } + + private static Map getVendorProperties(ConfigurableApplicationContext context) { + return context.getBean(HibernateJpaConfiguration.class).getVendorProperties(context.getBean(DataSource.class)); } @Test diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java index 8d26116a735..76a05743a0c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java @@ -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> 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 createJpaProperties(DataSource dataSource, Map existingProperties) { + Map jpaProperties = new LinkedHashMap<>(existingProperties); + // ... map JPA properties that require the DataSource (e.g. DDL flags) + return jpaProperties; + } + } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt index 55eefdd7e58..4b57a239fde 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt @@ -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): Map { + val jpaProperties: Map = LinkedHashMap(existingProperties) + // ... map JPA properties that require the DataSource (e.g. DDL flags) + return jpaProperties + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityManagerFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityManagerFactoryBuilder.java index 8091f99b06a..15bbcea920c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityManagerFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityManagerFactoryBuilder.java @@ -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 jpaProperties; + private final Function> 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> 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> 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 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 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 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);