Skip to content

Commit 04f3975

Browse files
committed
Support for qualified EntityManager/EntityManagerFactory injection (JPA 3.2)
Closes gh-33414
1 parent b0eacd2 commit 04f3975

File tree

11 files changed

+200
-71
lines changed

11 files changed

+200
-71
lines changed

spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
import org.springframework.beans.factory.BeanFactoryAware;
5454
import org.springframework.beans.factory.BeanNameAware;
5555
import org.springframework.beans.factory.DisposableBean;
56-
import org.springframework.beans.factory.FactoryBean;
5756
import org.springframework.beans.factory.InitializingBean;
57+
import org.springframework.beans.factory.SmartFactoryBean;
5858
import org.springframework.beans.factory.SmartInitializingSingleton;
5959
import org.springframework.core.task.AsyncTaskExecutor;
6060
import org.springframework.dao.DataAccessException;
@@ -66,7 +66,9 @@
6666
/**
6767
* Abstract {@link org.springframework.beans.factory.FactoryBean} that creates
6868
* a local JPA {@link jakarta.persistence.EntityManagerFactory} instance within
69-
* a Spring application context.
69+
* a Spring application context. As of 7.0, it additionally exposes a shared
70+
* {@link jakarta.persistence.EntityManager} instance through {@link SmartFactoryBean},
71+
* making {@code EntityManager} available for dependency injection as well.
7072
*
7173
* <p>Encapsulates the common functionality between the different JPA bootstrap
7274
* contracts (standalone as well as container).
@@ -91,7 +93,7 @@
9193
*/
9294
@SuppressWarnings("serial")
9395
public abstract class AbstractEntityManagerFactoryBean implements
94-
FactoryBean<EntityManagerFactory>, BeanClassLoaderAware, BeanFactoryAware,
96+
SmartFactoryBean<EntityManagerFactory>, BeanClassLoaderAware, BeanFactoryAware,
9597
BeanNameAware, InitializingBean, SmartInitializingSingleton, DisposableBean,
9698
EntityManagerFactoryInfo, PersistenceExceptionTranslator, Serializable {
9799

@@ -131,6 +133,9 @@ public abstract class AbstractEntityManagerFactoryBean implements
131133
/** Exposed client-level EntityManagerFactory proxy. */
132134
private @Nullable EntityManagerFactory entityManagerFactory;
133135

136+
/** Exposed client-level shared EntityManager proxy. */
137+
private @Nullable EntityManager sharedEntityManager;
138+
134139

135140
/**
136141
* Set the PersistenceProvider implementation class to use for creating the
@@ -333,11 +338,19 @@ public void setBeanFactory(BeanFactory beanFactory) {
333338
this.beanFactory = beanFactory;
334339
}
335340

341+
protected @Nullable BeanFactory getBeanFactory() {
342+
return this.beanFactory;
343+
}
344+
336345
@Override
337346
public void setBeanName(String name) {
338347
this.beanName = name;
339348
}
340349

350+
protected @Nullable String getBeanName() {
351+
return this.beanName;
352+
}
353+
341354

342355
@Override
343356
public void afterPropertiesSet() throws PersistenceException {
@@ -386,6 +399,7 @@ public void afterPropertiesSet() throws PersistenceException {
386399
// application-managed EntityManager proxy that automatically joins
387400
// existing transactions.
388401
this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
402+
this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory);
389403
}
390404

391405
@Override
@@ -621,9 +635,23 @@ public Class<? extends EntityManagerFactory> getObjectType() {
621635
return (this.entityManagerFactory != null ? this.entityManagerFactory.getClass() : EntityManagerFactory.class);
622636
}
623637

638+
/**
639+
* Return either the singleton EntityManagerFactory or the shared EntityManager proxy.
640+
*/
641+
@Override
642+
public <S> @Nullable S getObject(Class<S> type) throws Exception {
643+
if (EntityManager.class.isAssignableFrom(type)) {
644+
return (type.isInstance(this.sharedEntityManager) ? type.cast(this.sharedEntityManager) : null);
645+
}
646+
return SmartFactoryBean.super.getObject(type);
647+
}
648+
624649
@Override
625-
public boolean isSingleton() {
626-
return true;
650+
public boolean supportsType(Class<?> type) {
651+
if (EntityManager.class.isAssignableFrom(type)) {
652+
return type.isInstance(this.sharedEntityManager);
653+
}
654+
return SmartFactoryBean.super.supportsType(type);
627655
}
628656

629657

spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.orm.jpa;
1818

19+
import java.util.List;
20+
1921
import javax.sql.DataSource;
2022

2123
import jakarta.persistence.EntityManagerFactory;
@@ -27,6 +29,11 @@
2729
import org.jspecify.annotations.Nullable;
2830

2931
import org.springframework.beans.BeanUtils;
32+
import org.springframework.beans.factory.BeanFactory;
33+
import org.springframework.beans.factory.config.BeanDefinition;
34+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
35+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
36+
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
3037
import org.springframework.context.ResourceLoaderAware;
3138
import org.springframework.context.weaving.LoadTimeWeaverAware;
3239
import org.springframework.core.io.ResourceLoader;
@@ -40,6 +47,8 @@
4047
import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
4148
import org.springframework.util.Assert;
4249
import org.springframework.util.ClassUtils;
50+
import org.springframework.util.CollectionUtils;
51+
import org.springframework.util.StringUtils;
4352

4453
/**
4554
* {@link org.springframework.beans.factory.FactoryBean} that creates a JPA
@@ -361,6 +370,25 @@ public void afterPropertiesSet() throws PersistenceException {
361370
}
362371
}
363372

373+
String scope = this.persistenceUnitInfo.getScopeAnnotationName();
374+
if (StringUtils.hasText(scope)) {
375+
logger.info("Scope annotation name for persistence unit ignored by Spring: " + scope);
376+
}
377+
378+
List<String> qualifiers = this.persistenceUnitInfo.getQualifierAnnotationNames();
379+
if (!CollectionUtils.isEmpty(qualifiers)) {
380+
BeanFactory beanFactory = getBeanFactory();
381+
String beanName = getBeanName();
382+
if (beanFactory instanceof ConfigurableBeanFactory cbf && beanName != null) {
383+
BeanDefinition bd = cbf.getMergedBeanDefinition(beanName);
384+
if (bd instanceof AbstractBeanDefinition abd) {
385+
for (String qualifier : qualifiers) {
386+
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
387+
}
388+
}
389+
}
390+
}
391+
364392
super.afterPropertiesSet();
365393
}
366394

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public class MutablePersistenceUnitInfo implements SmartPersistenceUnitInfo {
5252

5353
private @Nullable String persistenceProviderClassName;
5454

55+
private @Nullable String scopeAnnotationName;
56+
57+
private final List<String> qualifierAnnotationNames = new ArrayList<>();
58+
5559
private @Nullable PersistenceUnitTransactionType transactionType;
5660

5761
private @Nullable DataSource nonJtaDataSource;
@@ -76,7 +80,7 @@ public class MutablePersistenceUnitInfo implements SmartPersistenceUnitInfo {
7680

7781
private Properties properties = new Properties();
7882

79-
private String persistenceXMLSchemaVersion = "2.0";
83+
private String persistenceXMLSchemaVersion = "3.2";
8084

8185
private @Nullable String persistenceProviderPackageName;
8286

@@ -99,6 +103,24 @@ public void setPersistenceProviderClassName(@Nullable String persistenceProvider
99103
return this.persistenceProviderClassName;
100104
}
101105

106+
public void setScopeAnnotationName(@Nullable String scopeAnnotationName) {
107+
this.scopeAnnotationName = scopeAnnotationName;
108+
}
109+
110+
@Override
111+
public @Nullable String getScopeAnnotationName() {
112+
return this.scopeAnnotationName;
113+
}
114+
115+
public void addQualifierAnnotationName(String qualifierAnnotationName) {
116+
this.qualifierAnnotationNames.add(qualifierAnnotationName);
117+
}
118+
119+
@Override
120+
public List<String> getQualifierAnnotationNames() {
121+
return this.qualifierAnnotationNames;
122+
}
123+
102124
public void setTransactionType(PersistenceUnitTransactionType transactionType) {
103125
this.transactionType = transactionType;
104126
}
@@ -276,16 +298,6 @@ public ClassLoader getNewTempClassLoader() {
276298
throw new UnsupportedOperationException("getNewTempClassLoader not supported");
277299
}
278300

279-
@Override
280-
public @Nullable String getScopeAnnotationName() {
281-
return null;
282-
}
283-
284-
@Override
285-
public @Nullable List<String> getQualifierAnnotationNames() {
286-
return null;
287-
}
288-
289301

290302
@Override
291303
public String toString() {

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,32 @@ final class PersistenceUnitReader {
6161

6262
private static final String UNIT_NAME = "name";
6363

64-
private static final String MAPPING_FILE_NAME = "mapping-file";
65-
66-
private static final String JAR_FILE_URL = "jar-file";
64+
private static final String PROVIDER = "provider";
6765

68-
private static final String MANAGED_CLASS_NAME = "class";
66+
private static final String SCOPE = "scope";
6967

70-
private static final String PROPERTIES = "properties";
71-
72-
private static final String PROVIDER = "provider";
68+
private static final String QUALIFIER = "qualifier";
7369

7470
private static final String TRANSACTION_TYPE = "transaction-type";
7571

7672
private static final String JTA_DATA_SOURCE = "jta-data-source";
7773

7874
private static final String NON_JTA_DATA_SOURCE = "non-jta-data-source";
7975

76+
private static final String MAPPING_FILE_NAME = "mapping-file";
77+
78+
private static final String JAR_FILE_URL = "jar-file";
79+
80+
private static final String MANAGED_CLASS_NAME = "class";
81+
8082
private static final String EXCLUDE_UNLISTED_CLASSES = "exclude-unlisted-classes";
8183

8284
private static final String SHARED_CACHE_MODE = "shared-cache-mode";
8385

8486
private static final String VALIDATION_MODE = "validation-mode";
8587

88+
private static final String PROPERTIES = "properties";
89+
8690
private static final String META_INF = "META-INF";
8791

8892

@@ -200,6 +204,18 @@ SpringPersistenceUnitInfo parsePersistenceUnitInfo(
200204
// set unit name
201205
unitInfo.setPersistenceUnitName(persistenceUnit.getAttribute(UNIT_NAME).trim());
202206

207+
// provider
208+
String provider = DomUtils.getChildElementValueByTagName(persistenceUnit, PROVIDER);
209+
if (StringUtils.hasText(provider)) {
210+
unitInfo.setPersistenceProviderClassName(provider.trim());
211+
}
212+
213+
// scope
214+
String scope = DomUtils.getChildElementValueByTagName(persistenceUnit, SCOPE);
215+
if (StringUtils.hasText(scope)) {
216+
unitInfo.setScopeAnnotationName(scope.trim());
217+
}
218+
203219
// set transaction type
204220
String txType = persistenceUnit.getAttribute(TRANSACTION_TYPE).trim();
205221
if (StringUtils.hasText(txType)) {
@@ -217,12 +233,6 @@ SpringPersistenceUnitInfo parsePersistenceUnitInfo(
217233
unitInfo.setNonJtaDataSource(this.dataSourceLookup.getDataSource(nonJtaDataSource.trim()));
218234
}
219235

220-
// provider
221-
String provider = DomUtils.getChildElementValueByTagName(persistenceUnit, PROVIDER);
222-
if (StringUtils.hasText(provider)) {
223-
unitInfo.setPersistenceProviderClassName(provider.trim());
224-
}
225-
226236
// exclude unlisted classes
227237
Element excludeUnlistedClasses = DomUtils.getChildElementByTagName(persistenceUnit, EXCLUDE_UNLISTED_CLASSES);
228238
if (excludeUnlistedClasses != null) {
@@ -242,39 +252,24 @@ SpringPersistenceUnitInfo parsePersistenceUnitInfo(
242252
unitInfo.setValidationMode(ValidationMode.valueOf(validationMode));
243253
}
244254

245-
parseProperties(persistenceUnit, unitInfo);
246-
parseManagedClasses(persistenceUnit, unitInfo);
255+
parseQualifiers(persistenceUnit, unitInfo);
247256
parseMappingFiles(persistenceUnit, unitInfo);
248257
parseJarFiles(persistenceUnit, unitInfo);
258+
parseManagedClasses(persistenceUnit, unitInfo);
259+
parseProperties(persistenceUnit, unitInfo);
249260

250261
return unitInfo;
251262
}
252263

253264
/**
254-
* Parse the {@code property} XML elements.
265+
* Parse the {@code qualifier} XML elements.
255266
*/
256-
void parseProperties(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
257-
Element propRoot = DomUtils.getChildElementByTagName(persistenceUnit, PROPERTIES);
258-
if (propRoot == null) {
259-
return;
260-
}
261-
List<Element> properties = DomUtils.getChildElementsByTagName(propRoot, "property");
262-
for (Element property : properties) {
263-
String name = property.getAttribute("name");
264-
String value = property.getAttribute("value");
265-
unitInfo.addProperty(name, value);
266-
}
267-
}
268-
269-
/**
270-
* Parse the {@code class} XML elements.
271-
*/
272-
void parseManagedClasses(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
273-
List<Element> classes = DomUtils.getChildElementsByTagName(persistenceUnit, MANAGED_CLASS_NAME);
267+
void parseQualifiers(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
268+
List<Element> classes = DomUtils.getChildElementsByTagName(persistenceUnit, QUALIFIER);
274269
for (Element element : classes) {
275270
String value = DomUtils.getTextValue(element).trim();
276271
if (StringUtils.hasText(value)) {
277-
unitInfo.addManagedClassName(value);
272+
unitInfo.addQualifierAnnotationName(value);
278273
}
279274
}
280275
}
@@ -323,6 +318,35 @@ void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo)
323318
}
324319
}
325320

321+
/**
322+
* Parse the {@code class} XML elements.
323+
*/
324+
void parseManagedClasses(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
325+
List<Element> classes = DomUtils.getChildElementsByTagName(persistenceUnit, MANAGED_CLASS_NAME);
326+
for (Element element : classes) {
327+
String value = DomUtils.getTextValue(element).trim();
328+
if (StringUtils.hasText(value)) {
329+
unitInfo.addManagedClassName(value);
330+
}
331+
}
332+
}
333+
334+
/**
335+
* Parse the {@code property} XML elements.
336+
*/
337+
void parseProperties(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
338+
Element propRoot = DomUtils.getChildElementByTagName(persistenceUnit, PROPERTIES);
339+
if (propRoot == null) {
340+
return;
341+
}
342+
List<Element> properties = DomUtils.getChildElementsByTagName(propRoot, "property");
343+
for (Element property : properties) {
344+
String name = property.getAttribute("name");
345+
String value = property.getAttribute("value");
346+
unitInfo.addProperty(name, value);
347+
}
348+
}
349+
326350

327351
/**
328352
* Determine the persistence unit root URL based on the given resource

spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryIntegrationTests.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.dao.DataAccessException;
3333
import org.springframework.jdbc.core.JdbcTemplate;
3434
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
35+
import org.springframework.orm.jpa.domain.MyDomain;
3536
import org.springframework.transaction.PlatformTransactionManager;
3637
import org.springframework.transaction.TransactionException;
3738
import org.springframework.transaction.TransactionStatus;
@@ -70,10 +71,14 @@ public abstract class AbstractEntityManagerFactoryIntegrationTests {
7071
private boolean zappedTables = false;
7172

7273

73-
@Autowired
74+
@Autowired @MyDomain
7475
public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
7576
this.entityManagerFactory = entityManagerFactory;
76-
this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory);
77+
}
78+
79+
@Autowired @MyDomain
80+
public void setSharedEntityManager(EntityManager sharedEntityManager) {
81+
this.sharedEntityManager = sharedEntityManager;
7782
}
7883

7984
@Autowired

0 commit comments

Comments
 (0)