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 94fa087dead..7cc91d73b12 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 @@ -86,14 +86,11 @@ import org.springframework.util.StringUtils; * @author Juergen Hoeller * @author Rod Johnson * @since 2.0 - * @see #setPersistenceXmlLocation * @see #setJpaProperties * @see #setJpaVendorAdapter * @see #setLoadTimeWeaver * @see #setDataSource - * @see EntityManagerFactoryInfo * @see LocalEntityManagerFactoryBean - * @see org.springframework.orm.jpa.support.SharedEntityManagerBean * @see jakarta.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory */ @SuppressWarnings("serial") 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 f6e8386dd3e..0880b12ac1e 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 @@ -16,6 +16,9 @@ package org.springframework.orm.jpa; +import java.util.LinkedHashSet; +import java.util.Set; + import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; @@ -25,8 +28,16 @@ import jakarta.persistence.PersistenceException; import jakarta.persistence.spi.PersistenceProvider; import org.jspecify.annotations.Nullable; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; +import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes; +import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypesScanner; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * {@link org.springframework.beans.factory.FactoryBean} that creates a JPA @@ -54,18 +65,26 @@ import org.springframework.util.Assert; * @since 2.0 * @see #setJpaProperties * @see #setJpaVendorAdapter - * @see JpaTransactionManager#setEntityManagerFactory + * @see #setPersistenceConfiguration + * @see #setDataSource * @see LocalContainerEntityManagerFactoryBean - * @see org.springframework.jndi.JndiObjectFactoryBean - * @see org.springframework.orm.jpa.support.SharedEntityManagerBean * @see jakarta.persistence.Persistence#createEntityManagerFactory * @see jakarta.persistence.spi.PersistenceProvider#createEntityManagerFactory */ @SuppressWarnings("serial") -public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean { +public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean implements ResourceLoaderAware { + + private static final String NON_JTA_DATASOURCE_PROPERTY = "jakarta.persistence.nonJtaDataSource"; + + private static final String PACKAGE_INFO_SUFFIX = ".package-info"; + private @Nullable PersistenceConfiguration configuration; + private String @Nullable [] packagesToScan; + + private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + /** * Create a {@code LocalEntityManagerFactoryBean}. @@ -151,6 +170,27 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB super.setPersistenceUnitName(persistenceUnitName); } + /** + * Set whether to use Spring-based scanning for entity classes in the classpath + * instead of using JPA's standard scanning of jar files with {@code persistence.xml} + * markers in them. In case of Spring-based scanning, no {@code persistence.xml} + * is necessary; all you need to do is to specify base packages to search here. + *

Default is none. Specify packages to search for autodetection of your entity + * classes in the classpath. This is analogous to Spring's component-scan feature + * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). + *

The use of this setter switches this {@code LocalEntityManagerFactoryBean} + * to a {@link #getPersistenceConfiguration() PersistenceConfiguration}, with no + * {@code persistence.xml} reading or provider-driven scanning happening anymore. + * Further JPA settings can be applied on the local {@link PersistenceConfiguration} + * via {@link #getPersistenceConfiguration()}. + * @since 7.0.4 + * @see LocalContainerEntityManagerFactoryBean#setPackagesToScan + * @see org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean#setPackagesToScan + */ + public void setPackagesToScan(String... packagesToScan) { + this.packagesToScan = packagesToScan; + } + /** * Specify the JDBC DataSource that the JPA persistence provider is supposed * to use for accessing the database. This is an alternative to keeping the @@ -165,9 +205,11 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB public void setDataSource(@Nullable DataSource dataSource) { if (dataSource != null) { getJpaPropertyMap().put(PersistenceConfiguration.JDBC_DATASOURCE, dataSource); + getJpaPropertyMap().put(NON_JTA_DATASOURCE_PROPERTY, dataSource); } else { getJpaPropertyMap().remove(PersistenceConfiguration.JDBC_DATASOURCE); + getJpaPropertyMap().remove(NON_JTA_DATASOURCE_PROPERTY); } } @@ -182,6 +224,11 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB return (DataSource) getJpaPropertyMap().get(PersistenceConfiguration.JDBC_DATASOURCE); } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); + } + /** * Initialize the EntityManagerFactory for the given configuration. @@ -193,6 +240,25 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB logger.debug("Building JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'"); } + if (this.packagesToScan != null) { + PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(this.resourcePatternResolver); + PersistenceManagedTypes result = scanner.scan(this.packagesToScan); + // Expose managed class names from scan result (on JPA 4.0+, this includes + // everything meta-annotated with @Discoverable, even package-info classes) + Set classNameSet = new LinkedHashSet<>(result.getManagedClassNames()); + // Expose managed packages as package-info class names if not included already + // (accepted by PersistenceConfiguration on Hibernate as well as EclipseLink) + for (String managedPackage : result.getManagedPackages()) { + classNameSet.add(managedPackage + PACKAGE_INFO_SUFFIX); + } + // Expose pre-resolved Class references to PersistenceConfiguration. + PersistenceConfiguration config = getPersistenceConfiguration(); + ClassLoader classLoader = this.resourcePatternResolver.getClassLoader(); + for (String className : classNameSet) { + config.managedClass(ClassUtils.resolveClassName(className, classLoader)); + } + } + if (this.configuration != null) { this.configuration.properties(getJpaPropertyMap()); } @@ -204,8 +270,8 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB provider.createEntityManagerFactory(this.configuration) : provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap())); if (emf == null) { - throw new IllegalStateException( - "PersistenceProvider [" + provider + "] did not return an EntityManagerFactory for name '" + + throw new PersistenceException( + "PersistenceProvider [" + provider + "] could not find persistence unit for name '" + getPersistenceUnitName() + "'"); } return emf; diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Car.java b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Car.java index 144e55fdfc4..7b9f3b59e2e 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Car.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Car.java @@ -47,4 +47,5 @@ public class Car { String getModel() { return model; } + } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Employee.java b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Employee.java index 4459399f50d..cdf456c0098 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Employee.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/Employee.java @@ -86,4 +86,5 @@ public class Employee { @PreRemove public void preRemove() { } + } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategory.java b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategory.java index 0b878f9058a..55054fa286a 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategory.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategory.java @@ -17,6 +17,7 @@ package org.springframework.orm.jpa.domain; public class EmployeeCategory { + private String name; public String getName() { @@ -26,4 +27,5 @@ public class EmployeeCategory { public void setName(String name) { this.name = name; } + } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategoryConverter.java b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategoryConverter.java index 3cccaee608c..2b6656dbf3c 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategoryConverter.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/domain/EmployeeCategoryConverter.java @@ -17,7 +17,9 @@ package org.springframework.orm.jpa.domain; import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +@Converter public class EmployeeCategoryConverter implements AttributeConverter { @Override @@ -37,4 +39,5 @@ public class EmployeeCategoryConverter implements AttributeConverter { @Override @@ -37,4 +39,5 @@ public class EmployeeKindConverter implements AttributeConverter candidates = List.of(Person.class.getName(), DriversLicense.class.getName()); PersistenceManagedTypes managedTypes = new PersistenceManagedTypesScanner( - RESOURCE_LOADER, candidates::contains).scan("org.springframework.orm.jpa.domain"); + resourceLoader, candidates::contains).scan("org.springframework.orm.jpa.domain"); assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder( Person.class.getName(), DriversLicense.class.getName()); assertThat(managedTypes.getManagedPackages()).isEmpty(); diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager-scan.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager-scan.xml new file mode 100644 index 00000000000..e8c02504b87 --- /dev/null +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager-scan.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + false + + + + + + + + + diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager-simple.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager-simple.xml new file mode 100644 index 00000000000..95cfc29af24 --- /dev/null +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager-simple.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + false + + + + + + + + + diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml index e2ef60dd32c..c535edd6d09 100644 --- a/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml @@ -1,14 +1,10 @@ - - - + - diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-scan.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-scan.xml new file mode 100644 index 00000000000..ba8ebdf8fc4 --- /dev/null +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-scan.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + org.springframework.orm.jpa.hibernate.SpringSessionContext + org.hibernate.cache.HashtableCacheProvider + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-simple.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-simple.xml new file mode 100644 index 00000000000..92e63b375a1 --- /dev/null +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-simple.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + org.springframework.orm.jpa.hibernate.SpringSessionContext + org.hibernate.cache.HashtableCacheProvider + + + + + + + + + + + + + + + + + + + + +