diff --git a/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java b/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java index bce4c30cde6..d4970653db9 100644 --- a/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java +++ b/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java @@ -351,6 +351,19 @@ class HibernateJpaAutoConfigurationTests { }); } + @Test + void customPersistenceUnitProcessorsAddedByServeralContributors() { + this.contextRunner.withUserConfiguration(TestConfigurationWithMultipleCustomPersistenceUnitPostProcessors.class) + .run((context) -> { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = context + .getBean(LocalContainerEntityManagerFactoryBean.class); + PersistenceUnitInfo persistenceUnitInfo = entityManagerFactoryBean.getPersistenceUnitInfo(); + assertThat(persistenceUnitInfo).isNotNull(); + assertThat(persistenceUnitInfo.getManagedClassNames()).contains( + "customized.attribute.converter.class.name", "customized.attribute.converter.class.anotherName"); + }); + } + @Test void customManagedClassNameFilter() { this.contextRunner.withBean(ManagedClassNameFilter.class, () -> (s) -> !s.endsWith("City")) @@ -1168,6 +1181,24 @@ class HibernateJpaAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @TestAutoConfigurationPackage(HibernateJpaAutoConfigurationTests.class) + static class TestConfigurationWithMultipleCustomPersistenceUnitPostProcessors { + + @Bean + EntityManagerFactoryBuilderCustomizer entityManagerFactoryBuilderCustomizer() { + return (builder) -> builder.addPersistenceUnitPostProcessors( + (pui) -> pui.addManagedClassName("customized.attribute.converter.class.name")); + } + + @Bean + EntityManagerFactoryBuilderCustomizer anotherEntityManagerFactoryBuilderCustomizer() { + return (builder) -> builder.addPersistenceUnitPostProcessors( + (pui) -> pui.addManagedClassName("customized.attribute.converter.class.anotherName")); + } + + } + static class CustomJpaTransactionManager extends JpaTransactionManager { } diff --git a/module/spring-boot-jpa/build.gradle b/module/spring-boot-jpa/build.gradle index 80e53b1ab8c..c0641897c37 100644 --- a/module/spring-boot-jpa/build.gradle +++ b/module/spring-boot-jpa/build.gradle @@ -36,7 +36,6 @@ dependencies { testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) - testImplementation("org.springframework:spring-context-support") } tasks.named("compileTestJava") { diff --git a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java index 91ea4f9993c..c23760d5e42 100644 --- a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java +++ b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java @@ -17,9 +17,12 @@ package org.springframework.boot.jpa; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -63,7 +66,7 @@ public class EntityManagerFactoryBuilder { private @Nullable AsyncTaskExecutor bootstrapExecutor; - private PersistenceUnitPostProcessor @Nullable [] persistenceUnitPostProcessors; + private @Nullable List persistenceUnitPostProcessors; /** * Create a new instance passing in the common pieces that will be shared if multiple @@ -126,7 +129,23 @@ public class EntityManagerFactoryBuilder { * @param persistenceUnitPostProcessors the persistence unit post processors to use */ public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... persistenceUnitPostProcessors) { - this.persistenceUnitPostProcessors = persistenceUnitPostProcessors; + this.persistenceUnitPostProcessors = new ArrayList<>(Arrays.asList(persistenceUnitPostProcessors)); + } + + /** + * Add {@linkplain PersistenceUnitPostProcessor persistence unit post processors} to + * be applied to the PersistenceUnitInfo used for creating the + * {@link LocalContainerEntityManagerFactoryBean}. + * @param persistenceUnitPostProcessors the persistence unit post processors to add + * @since 4.1.0 + */ + public void addPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... persistenceUnitPostProcessors) { + if (this.persistenceUnitPostProcessors != null) { + this.persistenceUnitPostProcessors.addAll(Arrays.asList(persistenceUnitPostProcessors)); + } + else { + setPersistenceUnitPostProcessors(persistenceUnitPostProcessors); + } } /** @@ -280,7 +299,8 @@ public class EntityManagerFactoryBuilder { } if (EntityManagerFactoryBuilder.this.persistenceUnitPostProcessors != null) { entityManagerFactoryBean - .setPersistenceUnitPostProcessors(EntityManagerFactoryBuilder.this.persistenceUnitPostProcessors); + .setPersistenceUnitPostProcessors(EntityManagerFactoryBuilder.this.persistenceUnitPostProcessors + .toArray(PersistenceUnitPostProcessor[]::new)); } return entityManagerFactoryBean; } diff --git a/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java b/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java new file mode 100644 index 00000000000..8e153dae603 --- /dev/null +++ b/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jpa; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +import javax.sql.DataSource; + +import jakarta.persistence.spi.PersistenceProvider; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; + +import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor; +import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link EntityManagerFactoryBuilder}. + * + * @author Stephane Nicoll + */ +class EntityManagerFactoryBuilderTests { + + @Test + void setPersistenceUnitPostProcessorsWhenEmpty() { + EntityManagerFactoryBuilder builder = createEmptyBuilder(); + PersistenceUnitPostProcessor postProcessor = mock(); + PersistenceUnitPostProcessor postProcessor2 = mock(); + builder.setPersistenceUnitPostProcessors(postProcessor, postProcessor2); + assertThat(builder).extracting("persistenceUnitPostProcessors") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactly(postProcessor, postProcessor2); + } + + @Test + void addPersistenceUnitPostProcessorsWhenEmpty() { + EntityManagerFactoryBuilder builder = createEmptyBuilder(); + PersistenceUnitPostProcessor postProcessor = mock(); + PersistenceUnitPostProcessor postProcessor2 = mock(); + builder.addPersistenceUnitPostProcessors(postProcessor, postProcessor2); + assertThat(builder).extracting("persistenceUnitPostProcessors") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactly(postProcessor, postProcessor2); + } + + @Test + void setPersistenceUnitPostProcessorsWhenNotEmpty() { + EntityManagerFactoryBuilder builder = createEmptyBuilder(); + PersistenceUnitPostProcessor postProcessor = mock(); + builder.addPersistenceUnitPostProcessors(postProcessor); + PersistenceUnitPostProcessor postProcessor2 = mock(); + PersistenceUnitPostProcessor postProcessor3 = mock(); + builder.setPersistenceUnitPostProcessors(postProcessor2, postProcessor3); + assertThat(builder).extracting("persistenceUnitPostProcessors") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactly(postProcessor2, postProcessor3); + } + + @Test + void addPersistenceUnitPostProcessorsWhenNotEmpty() { + EntityManagerFactoryBuilder builder = createEmptyBuilder(); + PersistenceUnitPostProcessor postProcessor = mock(); + builder.addPersistenceUnitPostProcessors(postProcessor); + PersistenceUnitPostProcessor postProcessor2 = mock(); + builder.addPersistenceUnitPostProcessors(postProcessor2); + assertThat(builder).extracting("persistenceUnitPostProcessors") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactly(postProcessor, postProcessor2); + } + + private EntityManagerFactoryBuilder createEmptyBuilder() { + Function> jpaPropertiesFactory = (dataSource) -> Collections + .emptyMap(); + return new EntityManagerFactoryBuilder(new TestJpaVendorAdapter(), jpaPropertiesFactory, null); + } + + static class TestJpaVendorAdapter extends AbstractJpaVendorAdapter { + + @Override + public PersistenceProvider getPersistenceProvider() { + return mock(); + } + + } + +}