diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java index 05c6bccf726..83b72032644 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java @@ -71,7 +71,9 @@ import org.springframework.util.CollectionUtils; * making {@code EntityManager} available for dependency injection as well. * *

Encapsulates the common functionality between the different JPA bootstrap - * contracts (standalone as well as container). + * contracts: standalone as well as container. Note that as of 7.0, the JPA 3.2 + * {@link LocalEntityManagerFactoryBean#setPersistenceConfiguration PersistenceConfiguration} + * mechanism is supported as well, allowing for much richer standalone bootstrap options. * *

Implements support for standard JPA configuration conventions as well as * Spring's customizable {@link JpaVendorAdapter} mechanism, and controls the diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java index 44cd372cb18..8b080269b49 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java @@ -21,6 +21,7 @@ import java.util.List; import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceException; import jakarta.persistence.SharedCacheMode; import jakarta.persistence.ValidationMode; @@ -166,6 +167,22 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage this.internalPersistenceUnitManager.setDefaultPersistenceUnitRootLocation(defaultPersistenceUnitRootLocation); } + /** + * Set a local JPA 3.2 {@link PersistenceConfiguration} to use for this + * persistence unit. + *

Note: {@link PersistenceConfiguration} includes a persistence unit name, + * so this effectively overrides the {@link #setPersistenceUnitName} method. + * In contrast, all other settings will be merged with the settings in the + * {@code PersistenceConfiguration} instance. + * @since 7.0 + * @see DefaultPersistenceUnitManager#setPersistenceConfiguration + */ + public void setPersistenceConfiguration(PersistenceConfiguration configuration) { + Assert.notNull(configuration, "PersistenceConfiguration must not be null"); + super.setPersistenceUnitName(configuration.name()); + this.internalPersistenceUnitManager.setPersistenceConfiguration(configuration); + } + /** * Set the {@link PersistenceManagedTypes} to use to build the list of managed types * as an alternative to entity scanning. @@ -424,15 +441,16 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage * Determine the PersistenceUnitInfo to use for the EntityManagerFactory * created by this bean. *

The default implementation reads in all persistence unit infos from - * {@code persistence.xml}, as defined in the JPA specification. - * If no entity manager name was specified, it takes the first info in the - * array as returned by the reader. Otherwise, it checks for a matching name. + * {@code persistence.xml}, as defined in the JPA specification, selecting a unit + * by name. If no persistence unit name was specified, it takes the default one + * if configured, or otherwise the first persistence unit as found by the reader. * @param persistenceUnitManager the PersistenceUnitManager to obtain from * @return the chosen PersistenceUnitInfo */ protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) { - if (getPersistenceUnitName() != null) { - return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName()); + String persistenceUnitName = getPersistenceUnitName(); + if (persistenceUnitName != null) { + return persistenceUnitManager.obtainPersistenceUnitInfo(persistenceUnitName); } else { return persistenceUnitManager.obtainDefaultPersistenceUnitInfo(); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java index 39d7490d35d..0eeb261730e 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java @@ -20,10 +20,14 @@ import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; +import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceException; import jakarta.persistence.spi.PersistenceProvider; import org.jspecify.annotations.Nullable; +import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; +import org.springframework.util.Assert; + /** * {@link org.springframework.beans.factory.FactoryBean} that creates a JPA * {@link jakarta.persistence.EntityManagerFactory} according to JPA's standard @@ -62,6 +66,80 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB private static final String DATASOURCE_PROPERTY = "jakarta.persistence.dataSource"; + private @Nullable PersistenceConfiguration configuration; + + + /** + * Create a {@code LocalEntityManagerFactoryBean}. + *

As of 7.0, This uses "default" for the default persistence unit name. + * @see #setPersistenceUnitName + * @see #setPersistenceConfiguration + */ + public LocalEntityManagerFactoryBean() { + setPersistenceUnitName(DefaultPersistenceUnitManager.ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME); + } + + /** + * Create a {@code LocalEntityManagerFactoryBean} for the given persistence unit. + * @param persistenceUnitName the name of the persistence unit + * @since 7.0 + */ + public LocalEntityManagerFactoryBean(String persistenceUnitName) { + setPersistenceUnitName(persistenceUnitName); + } + + /** + * Create a {@code LocalEntityManagerFactoryBean} for the given persistence unit. + * @param configuration the configuration for the persistence unit + * @since 7.0 + */ + public LocalEntityManagerFactoryBean(PersistenceConfiguration configuration) { + setPersistenceConfiguration(configuration); + } + + + /** + * Set a local JPA 3.2 {@link PersistenceConfiguration} to use for creating + * the EntityManagerFactory. This can be a provider-specific subclass such as + * {@link org.hibernate.jpa.HibernatePersistenceConfiguration}, exposing a + * complete programmatic persistence unit configuration which replaces + * {@code persistence.xml} (including provider-specific classpath scanning). + *

Note: {@link PersistenceConfiguration} includes a persistence unit name, + * so this effectively overrides the {@link #setPersistenceUnitName} method. + * In contrast, locally specified JPA properties ({@link #setJpaProperties}) + * will get merged into the given {@code PersistenceConfiguration} instance. + * @since 7.0 + * @see #getPersistenceConfiguration() + * @see #getPersistenceUnitName() + */ + public void setPersistenceConfiguration(PersistenceConfiguration configuration) { + Assert.notNull(configuration, "PersistenceConfiguration must not be null"); + this.configuration = configuration; + setPersistenceUnitName(configuration.name()); + } + + /** + * Set a local JPA 3.2 {@link PersistenceConfiguration} to use for creating + * the EntityManagerFactory. If none is in use yet, a new plain + * {@link PersistenceConfiguration} for the configured persistence unit name + * will be created and returned. + * @since 7.0 + * @see #setPersistenceConfiguration + * @see #setPersistenceUnitName + */ + public PersistenceConfiguration getPersistenceConfiguration() { + if (this.configuration == null) { + this.configuration = new PersistenceConfiguration(getPersistenceUnitName()); + } + return this.configuration; + } + + @Override + public void setPersistenceUnitName(@Nullable String persistenceUnitName) { + Assert.state(this.configuration == null || this.configuration.name().equals(persistenceUnitName), + "Cannot change setPersistenceUnitName when PersistenceConfiguration has been set"); + super.setPersistenceUnitName(persistenceUnitName); + } /** * Specify the JDBC DataSource that the JPA persistence provider is supposed @@ -104,10 +182,17 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB if (logger.isDebugEnabled()) { logger.debug("Building JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'"); } + + if (this.configuration != null) { + this.configuration.properties(getJpaPropertyMap()); + } + PersistenceProvider provider = getPersistenceProvider(); if (provider != null) { // Create EntityManagerFactory directly through PersistenceProvider. - EntityManagerFactory emf = provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()); + EntityManagerFactory emf = (this.configuration != null ? + provider.createEntityManagerFactory(this.configuration) : + provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap())); if (emf == null) { throw new IllegalStateException( "PersistenceProvider [" + provider + "] did not return an EntityManagerFactory for name '" + @@ -117,7 +202,9 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB } else { // Let JPA perform its standard PersistenceProvider autodetection. - return Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()); + return (this.configuration != null ? + Persistence.createEntityManagerFactory(this.configuration) : + Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap())); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java index 49e267f4343..c014ef43fc1 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java @@ -27,6 +27,7 @@ import java.util.Set; import javax.sql.DataSource; +import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceException; import jakarta.persistence.SharedCacheMode; import jakarta.persistence.ValidationMode; @@ -108,6 +109,8 @@ public class DefaultPersistenceUnitManager private @Nullable String defaultPersistenceUnitName = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME; + private @Nullable PersistenceConfiguration persistenceConfiguration; + private @Nullable PersistenceManagedTypes managedTypes; private String @Nullable [] packagesToScan; @@ -179,9 +182,19 @@ public class DefaultPersistenceUnitManager this.defaultPersistenceUnitName = defaultPersistenceUnitName; } + /** + * Set a JPA 3.2 {@link PersistenceConfiguration} to apply to the default + * persistence unit. The contained configuration will be merged with the + * common settings specified on this {@code DefaultPersistenceUnitManager}. + * @since 7.0 + */ + public void setPersistenceConfiguration(PersistenceConfiguration configuration) { + this.persistenceConfiguration = configuration; + } + /** * Set the {@link PersistenceManagedTypes} to use to build the list of managed types - * as an alternative to entity scanning. + * for the default persistence unit, as an alternative to entity scanning. * @param managedTypes the managed types * @since 6.0 */ @@ -492,7 +505,8 @@ public class DefaultPersistenceUnitManager private List readPersistenceUnitInfos() { List infos = new ArrayList<>(1); String defaultName = this.defaultPersistenceUnitName; - boolean buildDefaultUnit = (this.managedTypes != null || this.packagesToScan != null || this.mappingResources != null); + boolean buildDefaultUnit = (this.persistenceConfiguration != null || this.managedTypes != null || + this.packagesToScan != null || this.mappingResources != null); boolean foundDefaultUnit = false; PersistenceUnitReader reader = new PersistenceUnitReader(this.resourcePatternResolver, this.dataSourceLookup); @@ -507,8 +521,9 @@ public class DefaultPersistenceUnitManager if (buildDefaultUnit) { if (foundDefaultUnit) { if (logger.isWarnEnabled()) { - logger.warn("Found explicit default persistence unit with name '" + defaultName + "' in persistence.xml - " + - "overriding local default persistence unit settings ('managedTypes', 'packagesToScan' or 'mappingResources')"); + logger.warn("Found explicit default persistence unit with name '" + defaultName + + "' in persistence.xml - overriding local default persistence unit settings " + + "(`persistenceConfiguration`, 'managedTypes', 'packagesToScan' or 'mappingResources')"); } } else { @@ -523,33 +538,37 @@ public class DefaultPersistenceUnitManager * @see #setPackagesToScan */ private SpringPersistenceUnitInfo buildDefaultPersistenceUnitInfo() { - SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo(); + SpringPersistenceUnitInfo defaultUnit = new SpringPersistenceUnitInfo(); if (this.defaultPersistenceUnitName != null) { - scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName); + defaultUnit.setPersistenceUnitName(this.defaultPersistenceUnitName); + } + defaultUnit.setExcludeUnlistedClasses(true); + + if (this.persistenceConfiguration != null) { + defaultUnit.apply(this.persistenceConfiguration, this.dataSourceLookup); } - scannedUnit.setExcludeUnlistedClasses(true); if (this.managedTypes != null) { - applyManagedTypes(scannedUnit, this.managedTypes); + defaultUnit.apply(this.managedTypes); } else if (this.packagesToScan != null) { PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner( this.resourcePatternResolver, this.managedClassNameFilter); - applyManagedTypes(scannedUnit, scanner.scan(this.packagesToScan)); + defaultUnit.apply(scanner.scan(this.packagesToScan)); } if (this.mappingResources != null) { for (String mappingFileName : this.mappingResources) { - scannedUnit.addMappingFileName(mappingFileName); + defaultUnit.addMappingFileName(mappingFileName); } } else { Resource ormXml = getOrmXmlForDefaultPersistenceUnit(); if (ormXml != null) { - scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE); - if (scannedUnit.getPersistenceUnitRootUrl() == null) { + defaultUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE); + if (defaultUnit.getPersistenceUnitRootUrl() == null) { try { - scannedUnit.setPersistenceUnitRootUrl( + defaultUnit.setPersistenceUnitRootUrl( PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml)); } catch (IOException ex) { @@ -559,16 +578,7 @@ public class DefaultPersistenceUnitManager } } - return scannedUnit; - } - - private void applyManagedTypes(SpringPersistenceUnitInfo scannedUnit, PersistenceManagedTypes managedTypes) { - managedTypes.getManagedClassNames().forEach(scannedUnit::addManagedClassName); - managedTypes.getManagedPackages().forEach(scannedUnit::addManagedPackage); - URL persistenceUnitRootUrl = managedTypes.getPersistenceUnitRootUrl(); - if (scannedUnit.getPersistenceUnitRootUrl() == null && persistenceUnitRootUrl != null) { - scannedUnit.setPersistenceUnitRootUrl(persistenceUnitRootUrl); - } + return defaultUnit; } /** diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java index 35d02a65d59..5a66df719aa 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java @@ -60,16 +60,12 @@ public class MutablePersistenceUnitInfo { private @Nullable String persistenceProviderClassName; - private @Nullable String scopeAnnotationName; - - private final List qualifierAnnotationNames = new ArrayList<>(); - private @Nullable PersistenceUnitTransactionType transactionType; - private @Nullable DataSource nonJtaDataSource; - private @Nullable DataSource jtaDataSource; + private @Nullable DataSource nonJtaDataSource; + private final List mappingFileNames = new ArrayList<>(); private final List jarFileUrls = new ArrayList<>(); @@ -109,22 +105,6 @@ public class MutablePersistenceUnitInfo { return this.persistenceProviderClassName; } - public void setScopeAnnotationName(@Nullable String scopeAnnotationName) { - this.scopeAnnotationName = scopeAnnotationName; - } - - public @Nullable String getScopeAnnotationName() { - return this.scopeAnnotationName; - } - - public void addQualifierAnnotationName(String qualifierAnnotationName) { - this.qualifierAnnotationNames.add(qualifierAnnotationName); - } - - public List getQualifierAnnotationNames() { - return this.qualifierAnnotationNames; - } - public void setTransactionType(PersistenceUnitTransactionType transactionType) { this.transactionType = transactionType; } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java index 5c9a83682ec..d8b1ef37747 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java @@ -19,7 +19,11 @@ package org.springframework.orm.jpa.persistenceunit; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceUnitTransactionType; import jakarta.persistence.spi.ClassTransformer; import jakarta.persistence.spi.PersistenceUnitInfo; @@ -29,7 +33,9 @@ import org.jspecify.annotations.Nullable; import org.springframework.core.DecoratingClassLoader; import org.springframework.instrument.classloading.LoadTimeWeaver; import org.springframework.instrument.classloading.SimpleThrowawayClassLoader; +import org.springframework.jdbc.datasource.lookup.DataSourceLookup; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -55,6 +61,10 @@ public class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo { private @Nullable ClassLoader classLoader; + private @Nullable String scopeAnnotationName; + + private final List qualifierAnnotationNames = new ArrayList<>(); + /** * Construct a new SpringPersistenceUnitInfo for custom purposes. @@ -132,6 +142,87 @@ public class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo { return tcl; } + public void setScopeAnnotationName(@Nullable String scopeAnnotationName) { + this.scopeAnnotationName = scopeAnnotationName; + } + + public @Nullable String getScopeAnnotationName() { + return this.scopeAnnotationName; + } + + public void addQualifierAnnotationName(String qualifierAnnotationName) { + this.qualifierAnnotationNames.add(qualifierAnnotationName); + } + + public List getQualifierAnnotationNames() { + return this.qualifierAnnotationNames; + } + + + /** + * Apply the given {@link PersistenceManagedTypes} to this persistence unit, + * typically coming from Spring AOT. + * @param managedTypes the managed persistent types to register + * @since 7.0 + */ + public void apply(PersistenceManagedTypes managedTypes) { + Assert.notNull(managedTypes, "PersistenceManagedTypes must not be null"); + managedTypes.getManagedClassNames().forEach(this::addManagedClassName); + managedTypes.getManagedPackages().forEach(this::addManagedPackage); + URL persistenceUnitRootUrl = managedTypes.getPersistenceUnitRootUrl(); + if (getPersistenceUnitRootUrl() == null && persistenceUnitRootUrl != null) { + setPersistenceUnitRootUrl(persistenceUnitRootUrl); + } + } + + /** + * Apply the given JPA 3.2 {@link PersistenceConfiguration} to this persistence unit, + * copying all applicable settings. + *

Beyond the standard {@code PersistenceConfiguration} settings, "rootUrl" and + * "jarFileUrls" from {@link org.hibernate.jpa.HibernatePersistenceConfiguration} + * are also detected and applied. + * @param config the JPA persistence configuration to apply + * @param dataSourceLookup the JDBC DataSourceLookup that provides DataSources for the + * persistence provider, resolving data source names in {@code PersistenceConfiguration} + * against Spring-managed DataSource instances + * @since 7.0 + */ + @SuppressWarnings("unchecked") + public void apply(PersistenceConfiguration config, DataSourceLookup dataSourceLookup) { + Assert.notNull(config, "PersistenceConfiguration must not be null"); + + setPersistenceUnitName(config.name()); + setPersistenceProviderClassName(config.provider()); + setTransactionType(config.transactionType()); + + if (config.nonJtaDataSource() != null) { + setNonJtaDataSource(dataSourceLookup.getDataSource(config.nonJtaDataSource())); + } + if (config.jtaDataSource() != null) { + setJtaDataSource(dataSourceLookup.getDataSource(config.jtaDataSource())); + } + + config.mappingFiles().forEach(this::addMappingFileName); + config.managedClasses().forEach(clazz -> addManagedClassName(clazz.getName())); + + setSharedCacheMode(config.sharedCacheMode()); + setValidationMode(config.validationMode()); + getProperties().putAll(config.properties()); + + // Further relevant settings from HibernatePersistenceConfiguration + Method rootUrl = ClassUtils.getMethodIfAvailable(config.getClass(), "rootUrl"); + if (rootUrl != null) { + setPersistenceUnitRootUrl((URL) ReflectionUtils.invokeMethod(rootUrl, config)); + } + Method jarFileUrls = ClassUtils.getMethodIfAvailable(config.getClass(), "jarFileUrls"); + if (jarFileUrls != null) { + List urlList = ((List) ReflectionUtils.invokeMethod(jarFileUrls, config)); + if (urlList != null) { + urlList.forEach(this::addJarFileUrl); + } + } + } + /** * Expose a standard {@code jakarta.persistence.spi.PersistenceUnitInfo} proxy for the * persistence unit configuration in this {@code SpringPersistenceUnitInfo} instance. @@ -153,7 +244,7 @@ public class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo { */ private class SmartPersistenceUnitInfoInvocationHandler implements InvocationHandler { - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Fast path for SmartPersistenceUnitInfo JTA check diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java index fee9328518e..2473b46aab7 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java @@ -39,27 +39,27 @@ public abstract class AbstractEntityManagerFactoryBeanTests { protected static EntityManagerFactory mockEmf; + @BeforeEach - void setUp() { + void setup() { mockEmf = mock(); } @AfterEach - void tearDown() { + void cleanup() { assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); } - protected void checkInvariants(AbstractEntityManagerFactoryBean demf) { - assertThat(EntityManagerFactory.class.isAssignableFrom(demf.getObjectType())).isTrue(); - Object gotObject = demf.getObject(); - boolean condition = gotObject instanceof EntityManagerFactoryInfo; - assertThat(condition).as("Object created by factory implements EntityManagerFactoryInfo").isTrue(); - EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) demf.getObject(); - assertThat(demf.getObject()).as("Successive invocations of getObject() return same object").isSameAs(emfi); - assertThat(demf.getObject()).isSameAs(emfi); + protected void checkInvariants(AbstractEntityManagerFactoryBean emfb) { + assertThat(EntityManagerFactory.class.isAssignableFrom(emfb.getObjectType())).isTrue(); + EntityManagerFactory emf = emfb.getObject(); + assertThat(emf instanceof EntityManagerFactoryInfo).as("Object created by factory implements EntityManagerFactoryInfo").isTrue(); + EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) emf; + assertThat(emfb.getObject()).as("Successive invocations of getObject() return same object").isSameAs(emfi); + assertThat(emfb.getObject()).isSameAs(emfi); assertThat(mockEmf).isSameAs(emfi.getNativeEntityManagerFactory()); } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java index 57294dd321a..055c117402e 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java @@ -36,6 +36,8 @@ import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.dao.DataAccessException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; +import org.springframework.orm.jpa.domain.DriversLicense; +import org.springframework.orm.jpa.domain.Person; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @@ -53,23 +55,16 @@ import static org.mockito.Mockito.verify; * @author Juergen Hoeller * @author Phillip Webb */ -@SuppressWarnings({"rawtypes", "removal"}) +@SuppressWarnings("rawtypes") class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerFactoryBeanTests { - // Static fields set by inner class DummyPersistenceProvider - - private static Map actualProps; - - private static PersistenceUnitInfo actualPui; - - @Test - void testValidPersistenceUnit() throws Exception { + void validPersistenceUnit() { parseValidPersistenceUnit(); } @Test - void testExceptionTranslationWithNoDialect() throws Exception { + void exceptionTranslationWithNoDialect() { LocalContainerEntityManagerFactoryBean cefb = parseValidPersistenceUnit(); cefb.getObject(); assertThat(cefb.getJpaDialect()).as("No dialect set").isNull(); @@ -83,7 +78,7 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF } @Test - void testEntityManagerFactoryIsProxied() throws Exception { + void entityManagerFactoryIsProxied() throws Exception { LocalContainerEntityManagerFactoryBean cefb = parseValidPersistenceUnit(); EntityManagerFactory emf = cefb.getObject(); assertThat(cefb.getObject()).as("EntityManagerFactory reference must be cached after init").isSameAs(emf); @@ -100,7 +95,7 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF } @Test - void testApplicationManagedEntityManagerWithoutTransaction() throws Exception { + void applicationManagedEntityManagerWithoutTransaction() { Object testEntity = new Object(); EntityManager mockEm = mock(); @@ -120,7 +115,7 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF } @Test - void testApplicationManagedEntityManagerWithTransaction() throws Exception { + void applicationManagedEntityManagerWithTransaction() { Object testEntity = new Object(); EntityTransaction mockTx = mock(); @@ -161,7 +156,7 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF } @Test - void testApplicationManagedEntityManagerWithTransactionAndCommitException() throws Exception { + void applicationManagedEntityManagerWithTransactionAndCommitException() { Object testEntity = new Object(); EntityTransaction mockTx = mock(); @@ -203,7 +198,7 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF } @Test - void testApplicationManagedEntityManagerWithJtaTransaction() throws Exception { + void applicationManagedEntityManagerWithJtaTransaction() { Object testEntity = new Object(); // This one's for the tx (shared) @@ -240,67 +235,79 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF verify(mockEmf).close(); } - public LocalContainerEntityManagerFactoryBean parseValidPersistenceUnit( - PersistenceUnitPostProcessor... postProcessors) { + @Test + void invalidPersistenceUnitName() { + assertThatIllegalArgumentException().isThrownBy(() -> + createEntityManagerFactoryBean("org/springframework/orm/jpa/domain/persistence.xml", null, "call me Bob")); + } - return createEntityManagerFactoryBean( - "org/springframework/orm/jpa/domain/persistence.xml", null, - "Person", postProcessors); + @Test + void rejectsMissingPersistenceUnitInfo() { + LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); + emfb.setPersistenceProviderClass(DummyContainerPersistenceProvider.class); + + String entityManagerName = "call me Bob"; + emfb.setPersistenceUnitName(entityManagerName); + + assertThatIllegalArgumentException().isThrownBy(emfb::afterPropertiesSet); } @Test - void testInvalidPersistenceUnitName() { - assertThatIllegalArgumentException().isThrownBy(() -> - createEntityManagerFactoryBean("org/springframework/orm/jpa/domain/persistence.xml", null, "call me Bob")); + void acceptsPersistenceConfiguration() { + DummyContainerPersistenceProvider persistenceProvider = new DummyContainerPersistenceProvider(); + LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); + emfb.setPersistenceProvider(persistenceProvider); + + String entityManagerName = "call me Bob"; + emfb.setPersistenceConfiguration(new PersistenceConfiguration(entityManagerName). + managedClass(DriversLicense.class).managedClass(Person.class)); + + emfb.afterPropertiesSet(); + assertThat(persistenceProvider.actualPui.getPersistenceUnitName()).isEqualTo(entityManagerName); + assertThat(persistenceProvider.actualPui.getManagedClassNames()).containsExactly( + DriversLicense.class.getName(), Person.class.getName()); + } + + + private LocalContainerEntityManagerFactoryBean parseValidPersistenceUnit(PersistenceUnitPostProcessor... postProcessors) { + return createEntityManagerFactoryBean( + "org/springframework/orm/jpa/domain/persistence.xml", null, + "Person", postProcessors); } @SuppressWarnings("unchecked") - protected LocalContainerEntityManagerFactoryBean createEntityManagerFactoryBean( + private LocalContainerEntityManagerFactoryBean createEntityManagerFactoryBean( String persistenceXml, Properties props, String entityManagerName, PersistenceUnitPostProcessor... postProcessors) { - // This will be set by DummyPersistenceProvider - actualPui = null; - actualProps = null; + DummyContainerPersistenceProvider persistenceProvider = new DummyContainerPersistenceProvider(); + LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); - LocalContainerEntityManagerFactoryBean containerEmfb = new LocalContainerEntityManagerFactoryBean(); - - containerEmfb.setPersistenceUnitName(entityManagerName); - containerEmfb.setPersistenceProviderClass(DummyContainerPersistenceProvider.class); + emfb.setPersistenceUnitName(entityManagerName); + emfb.setPersistenceProvider(persistenceProvider); if (props != null) { - containerEmfb.setJpaProperties(props); + emfb.setJpaProperties(props); } - containerEmfb.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver()); - containerEmfb.setPersistenceXmlLocation(persistenceXml); - containerEmfb.setPersistenceUnitPostProcessors(postProcessors); - containerEmfb.afterPropertiesSet(); + emfb.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver()); + emfb.setPersistenceXmlLocation(persistenceXml); + emfb.setPersistenceUnitPostProcessors(postProcessors); + emfb.afterPropertiesSet(); - assertThat(actualPui.getPersistenceUnitName()).isEqualTo(entityManagerName); + assertThat(persistenceProvider.actualPui.getPersistenceUnitName()).isEqualTo(entityManagerName); if (props != null) { - assertThat(actualProps).isEqualTo(props); + assertThat(persistenceProvider.actualProps).isEqualTo(props); } - //checkInvariants(containerEmfb); - - return containerEmfb; + checkInvariants(emfb); - //containerEmfb.destroy(); - //emfMc.verify(); + return emfb; } - @Test - void testRejectsMissingPersistenceUnitInfo() { - LocalContainerEntityManagerFactoryBean containerEmfb = new LocalContainerEntityManagerFactoryBean(); - String entityManagerName = "call me Bob"; - - containerEmfb.setPersistenceUnitName(entityManagerName); - containerEmfb.setPersistenceProviderClass(DummyContainerPersistenceProvider.class); - assertThatIllegalArgumentException().isThrownBy( - containerEmfb::afterPropertiesSet); - } + private static class DummyContainerPersistenceProvider implements PersistenceProvider { + PersistenceUnitInfo actualPui; - private static class DummyContainerPersistenceProvider implements PersistenceProvider { + Map actualProps; @Override public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo pui, Map map) { diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java index da0d3e7fd9d..2c08105a7a0 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java @@ -27,64 +27,138 @@ import jakarta.persistence.spi.ProviderUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; /** * @author Rod Johnson + * @author Juergen Hoeller * @author Phillip Webb */ @SuppressWarnings("rawtypes") class LocalEntityManagerFactoryBeanTests extends AbstractEntityManagerFactoryBeanTests { - // Static fields set by inner class DummyPersistenceProvider - - private static String actualName; - - private static Map actualProps; - @AfterEach void verifyClosed() { verify(mockEmf).close(); } + @Test - void testValidUsageWithDefaultProperties() throws Exception { - testValidUsage(null); + void withDefault() { + DummyPersistenceProvider persistenceProvider = new DummyPersistenceProvider(); + LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); + + emfb.setPersistenceProvider(persistenceProvider); + emfb.afterPropertiesSet(); + + assertThat(persistenceProvider.actualName).isSameAs( + DefaultPersistenceUnitManager.ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME); + assertThat(persistenceProvider.actualProps).isEqualTo(emfb.getJpaPropertyMap()); + checkInvariants(emfb); + + emfb.destroy(); } @Test - void testValidUsageWithExplicitProperties() throws Exception { - testValidUsage(new Properties()); + void withName() { + DummyPersistenceProvider persistenceProvider = new DummyPersistenceProvider(); + LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); + String name = "call me Bob"; + + emfb.setPersistenceUnitName(name); + emfb.setPersistenceProvider(persistenceProvider); + emfb.afterPropertiesSet(); + + assertThat(persistenceProvider.actualName).isSameAs(name); + assertThat(persistenceProvider.actualProps).isEqualTo(emfb.getJpaPropertyMap()); + checkInvariants(emfb); + + emfb.destroy(); } - @SuppressWarnings("unchecked") - protected void testValidUsage(Properties props) { - // This will be set by DummyPersistenceProvider - actualName = null; - actualProps = null; + @Test + void withNameAndExplicitProperties() { + DummyPersistenceProvider persistenceProvider = new DummyPersistenceProvider(); + LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); + String name = "call me Bob"; + Properties props = new Properties(); + props.setProperty("myProp", "myVal"); + + emfb.setPersistenceUnitName(name); + emfb.setPersistenceProvider(persistenceProvider); + emfb.setJpaProperties(props); + emfb.afterPropertiesSet(); + + assertThat(persistenceProvider.actualName).isSameAs(name); + assertThat(persistenceProvider.actualProps).isEqualTo(props); + checkInvariants(emfb); + + emfb.destroy(); + } - LocalEntityManagerFactoryBean lemfb = new LocalEntityManagerFactoryBean(); - String entityManagerName = "call me Bob"; + @Test + void withDefaultPersistenceConfiguration() { + DummyPersistenceProvider persistenceProvider = new DummyPersistenceProvider(); + LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); - lemfb.setPersistenceUnitName(entityManagerName); - lemfb.setPersistenceProviderClass(DummyPersistenceProvider.class); - if (props != null) { - lemfb.setJpaProperties(props); - } - lemfb.afterPropertiesSet(); + emfb.getPersistenceConfiguration().property("myProp", "myVal"); + emfb.setPersistenceProvider(persistenceProvider); + emfb.afterPropertiesSet(); - assertThat(actualName).isSameAs(entityManagerName); - if (props != null) { - assertThat(actualProps).isEqualTo(props); - } - checkInvariants(lemfb); + assertThat(persistenceProvider.actualName).isSameAs( + DefaultPersistenceUnitManager.ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME); + assertThat(persistenceProvider.actualProps).containsEntry("myProp", "myVal"); + checkInvariants(emfb); + + emfb.destroy(); + } + + @Test + void withNameAndDefaultPersistenceConfiguration() { + DummyPersistenceProvider persistenceProvider = new DummyPersistenceProvider(); + LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); + String name = "call me Bob"; + + emfb.setPersistenceUnitName(name); + emfb.getPersistenceConfiguration().property("myProp", "myVal"); + emfb.setPersistenceProvider(persistenceProvider); + emfb.afterPropertiesSet(); + + assertThat(persistenceProvider.actualName).isSameAs(name); + assertThat(persistenceProvider.actualProps).containsEntry("myProp", "myVal"); + checkInvariants(emfb); - lemfb.destroy(); + emfb.destroy(); } + @Test + void withExplicitPersistenceConfiguration() { + DummyPersistenceProvider persistenceProvider = new DummyPersistenceProvider(); + LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); + String name = "call me Bob"; + PersistenceConfiguration config = new PersistenceConfiguration(name); + config.property("myProp", "myVal"); + + emfb.setPersistenceConfiguration(config); + emfb.setPersistenceProvider(persistenceProvider); + emfb.afterPropertiesSet(); + + assertThat(persistenceProvider.actualName).isSameAs(name); + assertThat(persistenceProvider.actualProps).containsEntry("myProp", "myVal"); + checkInvariants(emfb); + + emfb.destroy(); + } + + + private static class DummyPersistenceProvider implements PersistenceProvider { - protected static class DummyPersistenceProvider implements PersistenceProvider { + String actualName; + + Map actualProps; @Override public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo pui, Map map) { @@ -99,8 +173,10 @@ class LocalEntityManagerFactoryBeanTests extends AbstractEntityManagerFactoryBea } @Override - public EntityManagerFactory createEntityManagerFactory(PersistenceConfiguration persistenceConfiguration) { - throw new UnsupportedOperationException(); + public EntityManagerFactory createEntityManagerFactory(PersistenceConfiguration config) { + actualName = config.name(); + actualProps = config.properties(); + return mockEmf; } @Override @@ -108,13 +184,11 @@ class LocalEntityManagerFactoryBeanTests extends AbstractEntityManagerFactoryBea throw new UnsupportedOperationException(); } - // JPA 2.1 method @Override public void generateSchema(PersistenceUnitInfo persistenceUnitInfo, Map map) { throw new UnsupportedOperationException(); } - // JPA 2.1 method @Override public boolean generateSchema(String persistenceUnitName, Map map) { throw new UnsupportedOperationException(); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java index 3bd3ba4b3d0..f693018a469 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java @@ -46,7 +46,6 @@ import static org.assertj.core.api.Assertions.assertThatRuntimeException; * @author Juergen Hoeller * @author Nicholas Williams */ -@SuppressWarnings("removal") class PersistenceXmlParsingTests { @Test