Add setPackagesToScan to LocalEntityManagerFactoryBean (and make setDataSource work)

Includes fix for consistent PersistenceException in case of no unit found for name.
Includes proper tests for Local(Container)EntityManagerFactoryBean with scan setup.

Closes gh-36270
Closes gh-36271
This commit is contained in:
Juergen Hoeller
2026-02-08 17:54:02 +01:00
parent 05ffe8249f
commit 394a97d7e0
21 changed files with 393 additions and 21 deletions
@@ -47,4 +47,5 @@ public class Car {
String getModel() {
return model;
}
}
@@ -86,4 +86,5 @@ public class Employee {
@PreRemove
public void preRemove() {
}
}
@@ -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;
}
}
@@ -17,7 +17,9 @@
package org.springframework.orm.jpa.domain;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter
public class EmployeeCategoryConverter implements AttributeConverter<EmployeeCategory, String> {
@Override
@@ -37,4 +39,5 @@ public class EmployeeCategoryConverter implements AttributeConverter<EmployeeCat
}
return null;
}
}
@@ -17,6 +17,7 @@
package org.springframework.orm.jpa.domain;
public class EmployeeKind {
private String name;
public String getName() {
@@ -26,4 +27,5 @@ public class EmployeeKind {
public void setName(String name) {
this.name = name;
}
}
@@ -17,7 +17,9 @@
package org.springframework.orm.jpa.domain;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter(autoApply = true)
public class EmployeeKindConverter implements AttributeConverter<EmployeeKind, String> {
@Override
@@ -37,4 +39,5 @@ public class EmployeeKindConverter implements AttributeConverter<EmployeeKind, S
}
return null;
}
}
@@ -27,4 +27,5 @@ public class EmployeeLocation {
public void setLocation(String location) {
this.location = location;
}
}
@@ -39,4 +39,5 @@ public class EmployeeLocationConverter implements AttributeConverter<EmployeeLoc
}
return null;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2002-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.orm.jpa.eclipselink;
/**
* @author Juergen Hoeller
*/
class EclipseLinkEntityManagerFactoryScanIntegrationTests extends EclipseLinkEntityManagerFactoryIntegrationTests {
@Override
protected String[] getConfigLocations() {
return new String[] {"/org/springframework/orm/jpa/eclipselink/eclipselink-manager-scan.xml",
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml"};
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2002-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.orm.jpa.eclipselink;
import org.junit.jupiter.api.Test;
import org.springframework.orm.jpa.EntityManagerFactoryInfo;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Juergen Hoeller
*/
class EclipseLinkEntityManagerFactorySimpleIntegrationTests extends EclipseLinkEntityManagerFactoryIntegrationTests {
@Override
protected String[] getConfigLocations() {
return new String[] {"/org/springframework/orm/jpa/eclipselink/eclipselink-manager-simple.xml",
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml"};
}
@Test
protected void testEntityManagerFactoryImplementsEntityManagerFactoryInfo() {
assertThat(entityManagerFactory).isInstanceOf(EntityManagerFactoryInfo.class);
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) entityManagerFactory;
assertThat(emfi.getPersistenceUnitName()).isEqualTo("Person");
assertThat(emfi.getPersistenceUnitInfo()).as("PersistenceUnitInfo must not be used").isNull();
assertThat(emfi.getNativeEntityManagerFactory()).as("Raw EntityManagerFactory must be available").isNotNull();
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2002-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.orm.jpa.hibernate;
/**
* @author Juergen Hoeller
*/
class HibernateEntityManagerFactoryScanIntegrationTests extends HibernateEntityManagerFactoryIntegrationTests {
@Override
protected String[] getConfigLocations() {
return new String[] {"/org/springframework/orm/jpa/hibernate/hibernate-manager-scan.xml",
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml"};
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2002-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.orm.jpa.hibernate;
import org.junit.jupiter.api.Test;
import org.springframework.orm.jpa.EntityManagerFactoryInfo;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Juergen Hoeller
*/
class HibernateEntityManagerFactorySimpleIntegrationTests extends HibernateEntityManagerFactoryIntegrationTests {
@Override
protected String[] getConfigLocations() {
return new String[] {"/org/springframework/orm/jpa/hibernate/hibernate-manager-simple.xml",
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml"};
}
@Test
protected void testEntityManagerFactoryImplementsEntityManagerFactoryInfo() {
assertThat(entityManagerFactory).isInstanceOf(EntityManagerFactoryInfo.class);
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) entityManagerFactory;
assertThat(emfi.getPersistenceUnitName()).isEqualTo("Person");
assertThat(emfi.getPersistenceUnitInfo()).as("PersistenceUnitInfo must not be used").isNull();
assertThat(emfi.getNativeEntityManagerFactory()).as("Raw EntityManagerFactory must be available").isNotNull();
}
}
@@ -71,7 +71,8 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
PersistenceManagedTypes persistenceManagedTypes = freshApplicationContext.getBean(
"persistenceManagedTypes", PersistenceManagedTypes.class);
assertThat(persistenceManagedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
DriversLicense.class.getName(), Person.class.getName(), Employee.class.getName(),
Person.class.getName(), DriversLicense.class.getName(), Employee.class.getName(),
EmployeeCategoryConverter.class.getName(), EmployeeKindConverter.class.getName(),
EmployeeLocationConverter.class.getName(), Car.class.getName());
assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty();
assertThat(freshApplicationContext.getBean(
@@ -26,6 +26,8 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.orm.jpa.domain.Car;
import org.springframework.orm.jpa.domain.DriversLicense;
import org.springframework.orm.jpa.domain.Employee;
import org.springframework.orm.jpa.domain.EmployeeCategoryConverter;
import org.springframework.orm.jpa.domain.EmployeeKindConverter;
import org.springframework.orm.jpa.domain.EmployeeLocationConverter;
import org.springframework.orm.jpa.domain.Person;
import org.springframework.orm.jpa.domain2.entity.User;
@@ -44,15 +46,17 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
*/
class PersistenceManagedTypesScannerTests {
public static final DefaultResourceLoader RESOURCE_LOADER = new DefaultResourceLoader();
private final DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
private final PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(resourceLoader);
private final PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(RESOURCE_LOADER);
@Test
void scanPackageWithOnlyEntities() {
PersistenceManagedTypes managedTypes = this.scanner.scan("org.springframework.orm.jpa.domain");
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
Person.class.getName(), DriversLicense.class.getName(), Employee.class.getName(),
EmployeeCategoryConverter.class.getName(), EmployeeKindConverter.class.getName(),
EmployeeLocationConverter.class.getName(), Car.class.getName());
assertThat(managedTypes.getManagedPackages()).isEmpty();
}
@@ -61,11 +65,13 @@ class PersistenceManagedTypesScannerTests {
void scanPackageInvokesManagedClassNamesFilter() {
ManagedClassNameFilter filter = mock(ManagedClassNameFilter.class);
given(filter.matches(anyString())).willReturn(true);
new PersistenceManagedTypesScanner(RESOURCE_LOADER, filter)
new PersistenceManagedTypesScanner(resourceLoader, filter)
.scan("org.springframework.orm.jpa.domain");
verify(filter).matches(Person.class.getName());
verify(filter).matches(DriversLicense.class.getName());
verify(filter).matches(Employee.class.getName());
verify(filter).matches(EmployeeCategoryConverter.class.getName());
verify(filter).matches(EmployeeKindConverter.class.getName());
verify(filter).matches(EmployeeLocationConverter.class.getName());
verify(filter).matches(Car.class.getName());
verifyNoMoreInteractions(filter);
@@ -75,7 +81,7 @@ class PersistenceManagedTypesScannerTests {
void scanPackageWithUseManagedClassNamesFilter() {
List<String> 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();
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<qualifier type="org.springframework.orm.jpa.domain.MyDomain"/>
<property name="persistenceUnitName" value="Person"/>
<property name="packagesToScan" value="org.springframework.orm.jpa.domain*"/>
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="database" value="HSQL"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="jpaPropertyMap">
<props>
<prop key="eclipselink.weaving">false</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</beans>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<qualifier type="org.springframework.orm.jpa.domain.MyDomain"/>
<property name="persistenceUnitName" value="Person"/>
<property name="packagesToScan" value="org.springframework.orm.jpa.domain*"/>
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="database" value="HSQL"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="jpaPropertyMap">
<props>
<prop key="eclipselink.weaving">false</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</beans>
@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="org/springframework/orm/jpa/domain/persistence.xml"/>
<!--
<property name="persistenceUnitName" value="Person"/>
<property name="packagesToScan" value="org.springframework.orm.jpa.domain"/>
-->
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" primary="true">
<qualifier type="org.springframework.orm.jpa.domain.MyDomain"/>
<property name="persistenceUnitName" value="Person"/>
<property name="packagesToScan" value="org.springframework.orm.jpa.domain*"/>
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="HSQL"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="jpaPropertyMap">
<props>
<prop key="hibernate.current_session_context_class">org.springframework.orm.jpa.hibernate.SpringSessionContext</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
</props>
</property>
<property name="bootstrapExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="hibernateStatistics" factory-bean="entityManagerFactory" factory-method="getStatistics" lazy-init="true"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut"><bean class="org.springframework.orm.jpa.EntityManagerPointcut"/></property>
<property name="advice"><bean class="org.springframework.aop.interceptor.SimpleTraceInterceptor"/></property>
</bean>
</beans>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean" primary="true">
<qualifier type="org.springframework.orm.jpa.domain.MyDomain"/>
<property name="persistenceUnitName" value="Person"/>
<property name="packagesToScan" value="org.springframework.orm.jpa.domain*"/>
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="HSQL"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="jpaPropertyMap">
<props>
<prop key="hibernate.current_session_context_class">org.springframework.orm.jpa.hibernate.SpringSessionContext</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
</props>
</property>
<property name="bootstrapExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="hibernateStatistics" factory-bean="entityManagerFactory" factory-method="getStatistics" lazy-init="true"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut"><bean class="org.springframework.orm.jpa.EntityManagerPointcut"/></property>
<property name="advice"><bean class="org.springframework.aop.interceptor.SimpleTraceInterceptor"/></property>
</bean>
</beans>