Skip to content

Commit 176b945

Browse files
committed
Deprecate SharedEntityManager bean registration in favor of JPA 3.2 qualified EntityManager injection.
We now no longer register a SharedEntityManager bean if the EntityManagerFactors is created by AbstractEntityManagerFactoryBean. Closes #3926
1 parent ba97c46 commit 176b945

10 files changed

+146
-49
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@
4343
import org.springframework.beans.factory.ObjectProvider;
4444
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
4545
import org.springframework.beans.factory.config.BeanDefinition;
46+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4647
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4748
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4849
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
4950
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
5051
import org.springframework.beans.factory.support.RootBeanDefinition;
52+
import org.springframework.context.ConfigurableApplicationContext;
5153
import org.springframework.context.annotation.AnnotationConfigUtils;
5254
import org.springframework.core.annotation.AnnotationAttributes;
5355
import org.springframework.core.env.Environment;
@@ -58,7 +60,6 @@
5860
import org.springframework.data.jpa.repository.JpaRepository;
5961
import org.springframework.data.jpa.repository.aot.JpaRepositoryContributor;
6062
import org.springframework.data.jpa.repository.support.DefaultJpaContext;
61-
import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor;
6263
import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension;
6364
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
6465
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
@@ -68,9 +69,11 @@
6869
import org.springframework.data.repository.config.RepositoryConfigurationSource;
6970
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
7071
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
72+
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
7173
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
7274
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
7375
import org.springframework.util.ClassUtils;
76+
import org.springframework.util.ObjectUtils;
7477
import org.springframework.util.StringUtils;
7578

7679
/**
@@ -192,10 +195,6 @@ public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConf
192195

193196
Object source = config.getSource();
194197

195-
registerLazyIfNotAlreadyRegistered(
196-
() -> new RootBeanDefinition(EntityManagerBeanDefinitionRegistrarPostProcessor.class), registry,
197-
EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME, source);
198-
199198
registerLazyIfNotAlreadyRegistered(() -> new RootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class),
200199
registry, JPA_MAPPING_CONTEXT_BEAN_NAME, source);
201200

@@ -230,10 +229,21 @@ public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConf
230229
}, registry, JpaEvaluationContextExtension.class.getName(), source);
231230
}
232231

233-
private String registerSharedEntityMangerIfNotAlreadyRegistered(BeanDefinitionRegistry registry,
232+
private void registerSharedEntityMangerIfNotAlreadyRegistered(BeanDefinitionRegistry registry,
234233
RepositoryConfigurationSource config) {
235234

236235
String entityManagerBeanRef = getEntityManagerBeanRef(config);
236+
String sharedEntityManagerBeanRef = lookupSharedEntityManagerBeanRef(entityManagerBeanRef, registry);
237+
238+
// TODO: Remove once Cannot convert value of type 'jdk.proxy2.$Proxy129 implementing
239+
// org.hibernate.SessionFactory,org.springframework.orm.jpa.EntityManagerFactoryInfo' to required type
240+
// 'jakarta.persistence.EntityManager' for property 'entityManager': no matching editors or conversion strategy
241+
// found is fixed.
242+
/*if (sharedEntityManagerBeanRef != null) {
243+
entityManagerRefs.put(config, sharedEntityManagerBeanRef);
244+
return;
245+
} */
246+
237247
String entityManagerBeanName = "jpaSharedEM_" + entityManagerBeanRef;
238248

239249
if (!registry.containsBeanDefinition(entityManagerBeanName)) {
@@ -247,7 +257,39 @@ private String registerSharedEntityMangerIfNotAlreadyRegistered(BeanDefinitionRe
247257
}
248258

249259
entityManagerRefs.put(config, entityManagerBeanName);
250-
return entityManagerBeanName;
260+
}
261+
262+
private @Nullable String lookupSharedEntityManagerBeanRef(String entityManagerBeanRef,
263+
BeanDefinitionRegistry registry) {
264+
265+
if (!registry.containsBeanDefinition(entityManagerBeanRef)) {
266+
return null;
267+
}
268+
269+
BeanDefinitionRegistry introspect = registry;
270+
271+
if (introspect instanceof ConfigurableApplicationContext cac
272+
&& cac.getBeanFactory() instanceof BeanDefinitionRegistry br) {
273+
introspect = br;
274+
}
275+
276+
if (!(introspect instanceof ConfigurableBeanFactory cbf)) {
277+
return null;
278+
}
279+
280+
BeanDefinition beanDefinition = cbf.getMergedBeanDefinition(entityManagerBeanRef);
281+
282+
if (ObjectUtils.isEmpty(beanDefinition.getBeanClassName())) {
283+
return null;
284+
}
285+
286+
Class<?> beanClass = org.springframework.data.util.ClassUtils.loadIfPresent(beanDefinition.getBeanClassName(),
287+
getClass().getClassLoader());
288+
289+
// AbstractEntityManagerFactoryBean is able to create a SharedEntityManager
290+
return beanClass != null && AbstractEntityManagerFactoryBean.class.isAssignableFrom(beanClass)
291+
? entityManagerBeanRef
292+
: null;
251293
}
252294

253295
@Override

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultJpaContext.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
*/
1616
package org.springframework.data.jpa.repository.support;
1717

18-
import java.util.List;
19-
import java.util.Set;
20-
2118
import jakarta.persistence.EntityManager;
2219
import jakarta.persistence.metamodel.ManagedType;
2320

21+
import java.util.List;
22+
import java.util.Set;
23+
24+
import org.springframework.beans.factory.ObjectProvider;
25+
import org.springframework.beans.factory.annotation.Autowired;
2426
import org.springframework.data.jpa.repository.JpaContext;
2527
import org.springframework.util.Assert;
2628
import org.springframework.util.LinkedMultiValueMap;
@@ -37,6 +39,25 @@ public class DefaultJpaContext implements JpaContext {
3739

3840
private final MultiValueMap<Class<?>, EntityManager> entityManagers;
3941

42+
/**
43+
* Creates a new {@link DefaultJpaContext} for the given {@link Set} of {@link EntityManager}s.
44+
*
45+
* @param entityManagers must not be {@literal null}.
46+
*/
47+
@Autowired
48+
public DefaultJpaContext(ObjectProvider<EntityManager> entityManagers) {
49+
50+
Assert.notNull(entityManagers, "EntityManagerFactories must not be null");
51+
52+
this.entityManagers = new LinkedMultiValueMap<>();
53+
54+
for (EntityManager em : entityManagers) {
55+
for (ManagedType<?> managedType : em.getMetamodel().getManagedTypes()) {
56+
this.entityManagers.add(managedType.getJavaType(), em);
57+
}
58+
}
59+
}
60+
4061
/**
4162
* Creates a new {@link DefaultJpaContext} for the given {@link Set} of {@link EntityManager}s.
4263
*

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import jakarta.persistence.EntityManager;
2121
import jakarta.persistence.EntityManagerFactory;
2222

23+
import java.util.function.BiPredicate;
24+
2325
import org.springframework.beans.BeansException;
2426
import org.springframework.beans.factory.BeanFactory;
2527
import org.springframework.beans.factory.annotation.Qualifier;
@@ -44,9 +46,31 @@
4446
* @author Réda Housni Alaoui
4547
* @author Mark Paluch
4648
* @author Donghun Shin
49+
* @deprecated since 4.0, in favor of using either {@link org.springframework.orm.jpa.AbstractEntityManagerFactoryBean}
50+
* that provides a shared {@link EntityManager} or using {@link SharedEntityManagerCreator} directly in your
51+
* configuration.
4752
*/
53+
@Deprecated(since = "4.0")
4854
public class EntityManagerBeanDefinitionRegistrarPostProcessor implements BeanFactoryPostProcessor, Ordered {
4955

56+
private final BiPredicate<String, BeanDefinition> decoratorPredicate;
57+
58+
public EntityManagerBeanDefinitionRegistrarPostProcessor() {
59+
this((beanName, beanDefinition) -> true);
60+
}
61+
62+
/**
63+
* Creates a new {@code EntityManagerBeanDefinitionRegistrarPostProcessor} allowing to filter which
64+
* {@link EntityManagerFactory} beans should be decorated with a {@code SharedEntityManagerCreator}.
65+
*
66+
* @param decoratorPredicate the predicate to determine whether a given named {@link BeanDefinition} should be
67+
* decorated with a {@code SharedEntityManagerCreator}.
68+
* @since 4.0
69+
*/
70+
public EntityManagerBeanDefinitionRegistrarPostProcessor(BiPredicate<String, BeanDefinition> decoratorPredicate) {
71+
this.decoratorPredicate = decoratorPredicate;
72+
}
73+
5074
@Override
5175
public int getOrder() {
5276
return Ordered.HIGHEST_PRECEDENCE + 10;
@@ -55,7 +79,8 @@ public int getOrder() {
5579
@Override
5680
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
5781

58-
for (EntityManagerFactoryBeanDefinition definition : getEntityManagerFactoryBeanDefinitions(beanFactory)) {
82+
for (EntityManagerFactoryBeanDefinition definition : getEntityManagerFactoryBeanDefinitions(beanFactory,
83+
decoratorPredicate)) {
5984

6085
BeanFactory definitionFactory = definition.getBeanFactory();
6186

spring-data-jpa/src/main/java/org/springframework/data/jpa/util/BeanDefinitionUtils.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.HashSet;
2727
import java.util.List;
2828
import java.util.Set;
29+
import java.util.function.BiPredicate;
2930

3031
import org.springframework.beans.factory.BeanFactory;
3132
import org.springframework.beans.factory.ListableBeanFactory;
@@ -96,20 +97,37 @@ public static Iterable<String> getEntityManagerFactoryBeanNames(ListableBeanFact
9697
*/
9798
public static Collection<EntityManagerFactoryBeanDefinition> getEntityManagerFactoryBeanDefinitions(
9899
ConfigurableListableBeanFactory beanFactory) {
100+
return getEntityManagerFactoryBeanDefinitions(beanFactory, (beanName, beanDefinition) -> true);
101+
}
102+
103+
/**
104+
* Returns {@link EntityManagerFactoryBeanDefinition} instances for all {@link BeanDefinition} registered in the given
105+
* {@link ConfigurableListableBeanFactory} hierarchy.
106+
*
107+
* @param beanFactory must not be {@literal null}.
108+
* @param beanDefinitionBiPredicate predicate to determine whether a {@link EntityManagerFactory} bean should be
109+
* decorated with a {@code SharedEntityManager} bean definition.
110+
* @return
111+
* @since 4.0
112+
*/
113+
public static Collection<EntityManagerFactoryBeanDefinition> getEntityManagerFactoryBeanDefinitions(
114+
ConfigurableListableBeanFactory beanFactory, BiPredicate<String, BeanDefinition> beanDefinitionBiPredicate) {
99115

100116
Set<EntityManagerFactoryBeanDefinition> definitions = new HashSet<>();
101117

102118
for (Class<?> type : EMF_TYPES) {
103119

104120
for (String name : beanFactory.getBeanNamesForType(type, true, false)) {
105-
registerEntityManagerFactoryBeanDefinition(transformedBeanName(name), beanFactory, definitions);
121+
registerEntityManagerFactoryBeanDefinition(transformedBeanName(name), beanFactory, definitions,
122+
beanDefinitionBiPredicate);
106123
}
107124
}
108125

109126
BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
110127

111128
if (parentBeanFactory instanceof ConfigurableListableBeanFactory parentConfigurableListableBeanFactory) {
112-
definitions.addAll(getEntityManagerFactoryBeanDefinitions(parentConfigurableListableBeanFactory));
129+
definitions.addAll(
130+
getEntityManagerFactoryBeanDefinitions(parentConfigurableListableBeanFactory, beanDefinitionBiPredicate));
113131
}
114132

115133
return definitions;
@@ -122,9 +140,11 @@ public static Collection<EntityManagerFactoryBeanDefinition> getEntityManagerFac
122140
* @param name
123141
* @param beanFactory
124142
* @param definitions
143+
* @param decoratorPredicate
125144
*/
126145
private static void registerEntityManagerFactoryBeanDefinition(String name,
127-
ConfigurableListableBeanFactory beanFactory, Collection<EntityManagerFactoryBeanDefinition> definitions) {
146+
ConfigurableListableBeanFactory beanFactory, Collection<EntityManagerFactoryBeanDefinition> definitions,
147+
BiPredicate<String, BeanDefinition> decoratorPredicate) {
128148

129149
BeanDefinition definition = beanFactory.getBeanDefinition(name);
130150

@@ -139,7 +159,9 @@ private static void registerEntityManagerFactoryBeanDefinition(String name,
139159
return;
140160
}
141161

142-
definitions.add(new EntityManagerFactoryBeanDefinition(name, beanFactory));
162+
if (decoratorPredicate.test(name, definition)) {
163+
definitions.add(new EntityManagerFactoryBeanDefinition(name, beanFactory));
164+
}
143165
}
144166

145167
/**

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/AotFragmentTestConfigurationSupport.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package org.springframework.data.jpa.repository.aot;
1717

1818
import jakarta.persistence.EntityManager;
19-
import jakarta.persistence.EntityManagerFactory;
2019

2120
import java.lang.reflect.Method;
2221
import java.lang.reflect.Proxy;
@@ -28,12 +27,14 @@
2827
import org.springframework.beans.factory.config.BeanDefinition;
2928
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3029
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
30+
import org.springframework.beans.factory.config.RuntimeBeanReference;
3131
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3232
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3333
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3434
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
3535
import org.springframework.context.annotation.Bean;
3636
import org.springframework.context.annotation.ImportResource;
37+
import org.springframework.context.annotation.Primary;
3738
import org.springframework.core.env.StandardEnvironment;
3839
import org.springframework.core.io.DefaultResourceLoader;
3940
import org.springframework.core.test.tools.TestCompiler;
@@ -46,7 +47,7 @@
4647
import org.springframework.data.repository.core.RepositoryMetadata;
4748
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
4849
import org.springframework.data.repository.query.ValueExpressionDelegate;
49-
import org.springframework.orm.jpa.SharedEntityManagerCreator;
50+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
5051
import org.springframework.util.ReflectionUtils;
5152

5253
/**
@@ -76,6 +77,16 @@ EnableJpaRepositories.class, new DefaultResourceLoader(), new StandardEnvironmen
7677
Mockito.mock(BeanDefinitionRegistry.class), DefaultBeanNameGenerator.INSTANCE));
7778
}
7879

80+
// TODO: Remove once Cannot convert value of type 'jdk.proxy2.$Proxy129 implementing
81+
// org.hibernate.SessionFactory,org.springframework.orm.jpa.EntityManagerFactoryInfo' to required type
82+
// 'jakarta.persistence.EntityManager' for property 'entityManager': no matching editors or conversion strategy found
83+
// is fixed.
84+
@Bean
85+
@Primary
86+
EntityManager entityManager(LocalContainerEntityManagerFactoryBean factoryBean) throws Exception {
87+
return factoryBean.getObject(EntityManager.class);
88+
}
89+
7990
@Override
8091
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
8192

@@ -85,7 +96,7 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
8596

8697
AbstractBeanDefinition aotGeneratedRepository = BeanDefinitionBuilder
8798
.genericBeanDefinition(repositoryInterface.getName() + "Impl__Aot")
88-
.addConstructorArgReference("jpaSharedEM_entityManagerFactory")
99+
.addConstructorArgValue(new RuntimeBeanReference(EntityManager.class))
89100
.addConstructorArgValue(getCreationContext(repositoryContext)).getBeanDefinition();
90101

91102
TestCompiler.forSystem().withCompilerOptions("-parameters").with(generationContext).compile(compiled -> {
@@ -125,11 +136,6 @@ private Object getFragmentFacadeProxy(Object fragment) {
125136
});
126137
}
127138

128-
@Bean("jpaSharedEM_entityManagerFactory")
129-
EntityManager sharedEntityManagerCreator(EntityManagerFactory emf) {
130-
return SharedEntityManagerCreator.createSharedEntityManager(emf);
131-
}
132-
133139
private RepositoryFactoryBeanSupport.FragmentCreationContext getCreationContext(
134140
TestJpaAotRepositoryContext<?> repositoryContext) {
135141

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
import org.mockito.junit.jupiter.MockitoExtension;
3131
import org.mockito.junit.jupiter.MockitoSettings;
3232
import org.mockito.quality.Strictness;
33+
3334
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
34-
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3535
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
3636
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3737
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -149,20 +149,6 @@ void exposesJpaAotProcessor() {
149149
.isEqualTo(JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor.class);
150150
}
151151

152-
@Test // GH-2730
153-
void shouldNotRegisterEntityManagerAsSynthetic() {
154-
155-
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
156-
157-
RepositoryConfigurationExtension extension = new JpaRepositoryConfigExtension();
158-
extension.registerBeansForRoot(factory, configSource);
159-
160-
AbstractBeanDefinition bd = (AbstractBeanDefinition) factory.getBeanDefinition("jpaSharedEM_"
161-
+ configSource.getAttribute("entityManagerFactoryRef").orElse("entityManagerFactory"));
162-
163-
assertThat(bd.isSynthetic()).isEqualTo(false);
164-
}
165-
166152
private void assertOnlyOnePersistenceAnnotationBeanPostProcessorRegistered(DefaultListableBeanFactory factory,
167153
String expectedBeanName) {
168154

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextUnitTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20-
import java.util.Collections;
21-
2220
import jakarta.persistence.EntityManager;
2321

22+
import java.util.Collections;
23+
import java.util.Set;
24+
2425
import org.junit.jupiter.api.Test;
2526

2627
/**
@@ -34,7 +35,7 @@ class DefaultJpaContextUnitTests {
3435

3536
@Test // DATAJPA-669
3637
void rejectsNullEntityManagers() {
37-
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultJpaContext(null));
38+
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultJpaContext((Set<EntityManager>) null));
3839
}
3940

4041
@Test // DATAJPA-669

0 commit comments

Comments
 (0)