Make LocalEntityManagerFactoryBean#setDataSource work on Hibernate and EclipseLink

Includes fix for consistent PersistenceException in case of no unit found for name.
Includes proper tests for LocalContainerEntityManagerFactoryBean with scan setup.

Closes gh-36272
This commit is contained in:
Juergen Hoeller
2026-02-08 18:06:58 +01:00
parent a0319b1f91
commit bb35e9f11c
17 changed files with 157 additions and 17 deletions
@@ -75,14 +75,11 @@ import org.springframework.util.ClassUtils;
* @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")
@@ -51,10 +51,8 @@ import org.springframework.lang.Nullable;
* @since 2.0
* @see #setJpaProperties
* @see #setJpaVendorAdapter
* @see JpaTransactionManager#setEntityManagerFactory
* @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
*/
@@ -63,6 +61,8 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
private static final String DATASOURCE_PROPERTY = "jakarta.persistence.dataSource";
private static final String NON_JTA_DATASOURCE_PROPERTY = "jakarta.persistence.nonJtaDataSource";
/**
* Specify the JDBC DataSource that the JPA persistence provider is supposed
@@ -78,9 +78,11 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
public void setDataSource(@Nullable DataSource dataSource) {
if (dataSource != null) {
getJpaPropertyMap().put(DATASOURCE_PROPERTY, dataSource);
getJpaPropertyMap().put(NON_JTA_DATASOURCE_PROPERTY, dataSource);
}
else {
getJpaPropertyMap().remove(DATASOURCE_PROPERTY);
getJpaPropertyMap().remove(NON_JTA_DATASOURCE_PROPERTY);
}
}
@@ -111,8 +113,8 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
// Create EntityManagerFactory directly through PersistenceProvider.
EntityManagerFactory emf = 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;
@@ -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,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"};
}
}
@@ -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,28 @@
<?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">
<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>
@@ -5,10 +5,6 @@
<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,36 @@
<?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">
<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"/>
</beans>