Skip to content

Commit 2489e01

Browse files
committed
Fix PersistenceProvider lookup using proxied EntityManagerFactory.
We now distinguish between Spring-proxied and other JDK proxied EntityManagerFactory objects for proper unwrapping. Spring consistently uses a null value as class to get hold of the target object. Both, Hibernate and EclipseLink fail with a NullPointerException when calling unwrap(null) and therefore, we call all other JDK proxies with unwrap(EntityManagerFactory.class) to adhere to the JPA specification and avoid failures according to the implementations. Any other proxying mechanism that behaves differently will require additional refinement once such a case comes up. Closes #3923
1 parent faa526f commit 2489e01

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.util.List;
3232
import java.util.NoSuchElementException;
3333
import java.util.Set;
34-
import java.util.function.LongSupplier;
3534
import java.util.stream.Stream;
3635

3736
import org.eclipse.persistence.config.QueryHints;
@@ -270,7 +269,10 @@ public static PersistenceProvider fromEntityManagerFactory(EntityManagerFactory
270269
while (Proxy.isProxyClass(unwrapped.getClass()) || AopUtils.isAopProxy(unwrapped)) {
271270

272271
if (Proxy.isProxyClass(unwrapped.getClass())) {
273-
unwrapped = unwrapped.unwrap(null);
272+
273+
Class<EntityManagerFactory> unwrapTo = Proxy.getInvocationHandler(unwrapped).getClass().getName()
274+
.contains("org.springframework.orm.jpa.") ? null : EntityManagerFactory.class;
275+
unwrapped = unwrapped.unwrap(unwrapTo);
274276
} else if (AopUtils.isAopProxy(unwrapped)) {
275277
unwrapped = (EntityManagerFactory) AopProxyUtils.getSingletonTarget(unwrapped);
276278
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
import jakarta.persistence.EntityManager;
2424
import jakarta.persistence.EntityManagerFactory;
25+
import jakarta.persistence.PersistenceException;
2526

27+
import java.lang.reflect.Proxy;
2628
import java.util.Arrays;
2729
import java.util.Map;
2830

@@ -35,6 +37,7 @@
3537
import org.springframework.asm.ClassWriter;
3638
import org.springframework.asm.Opcodes;
3739
import org.springframework.instrument.classloading.ShadowingClassLoader;
40+
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
3841
import org.springframework.test.util.ReflectionTestUtils;
3942
import org.springframework.util.ClassUtils;
4043

@@ -110,6 +113,25 @@ void detectsProviderFromProxiedEntityManager() throws Exception {
110113
assertThat(fromEntityManager(emProxy)).isEqualTo(ECLIPSELINK);
111114
}
112115

116+
@Test // GH-3923
117+
void detectsEntityManagerFromProxiedEntityManagerFactory() throws Exception {
118+
119+
EntityManagerFactory emf = mockProviderSpecificEntityManagerFactoryInterface(
120+
"foo.bar.unknown.jpa.JpaEntityManager");
121+
when(emf.unwrap(null)).thenThrow(new NullPointerException());
122+
when(emf.unwrap(EntityManagerFactory.class)).thenReturn(emf);
123+
124+
MyEntityManagerFactoryBean factoryBean = new MyEntityManagerFactoryBean(EntityManagerFactory.class, emf);
125+
EntityManagerFactory springProxy = factoryBean.createEntityManagerFactoryProxy(emf);
126+
127+
Object externalProxy = Proxy.newProxyInstance(getClass().getClassLoader(),
128+
new Class[] { EntityManagerFactory.class }, (proxy, method, args) -> method.invoke(emf, args));
129+
130+
assertThat(PersistenceProvider.fromEntityManagerFactory(springProxy)).isEqualTo(GENERIC_JPA);
131+
assertThat(PersistenceProvider.fromEntityManagerFactory((EntityManagerFactory) externalProxy))
132+
.isEqualTo(GENERIC_JPA);
133+
}
134+
113135
private EntityManager mockProviderSpecificEntityManagerInterface(String interfaceName) throws ClassNotFoundException {
114136

115137
Class<?> providerSpecificEntityManagerInterface = InterfaceGenerator.generate(interfaceName, shadowingClassLoader,
@@ -128,7 +150,7 @@ private EntityManagerFactory mockProviderSpecificEntityManagerFactoryInterface(S
128150
throws ClassNotFoundException {
129151

130152
Class<?> providerSpecificEntityManagerInterface = InterfaceGenerator.generate(interfaceName, shadowingClassLoader,
131-
EntityManager.class);
153+
EntityManagerFactory.class);
132154

133155
return (EntityManagerFactory) Mockito.mock(providerSpecificEntityManagerInterface);
134156
}
@@ -181,4 +203,24 @@ private static String[] toResourcePaths(Class<?>... interfacesToImplement) {
181203
.toArray(String[]::new);
182204
}
183205
}
206+
207+
static class MyEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean {
208+
209+
public MyEntityManagerFactoryBean(Class<? extends EntityManagerFactory> entityManagerFactoryInterface,
210+
EntityManagerFactory entityManagerFactory) {
211+
setEntityManagerFactoryInterface(entityManagerFactoryInterface);
212+
ReflectionTestUtils.setField(this, "nativeEntityManagerFactory", entityManagerFactory);
213+
214+
}
215+
216+
@Override
217+
protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {
218+
return null;
219+
}
220+
221+
@Override
222+
protected EntityManagerFactory createEntityManagerFactoryProxy(EntityManagerFactory emf) {
223+
return super.createEntityManagerFactoryProxy(emf);
224+
}
225+
}
184226
}

0 commit comments

Comments
 (0)