diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java index f00f4b849..1ae908e37 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java @@ -58,7 +58,7 @@ abstract class JpaClassUtils { return isOfType(metamodel, type, metamodel.getClass().getClassLoader()); } - private static boolean isOfType(Object source, String typeName, @Nullable ClassLoader classLoader) { + static boolean isOfType(Object source, String typeName, @Nullable ClassLoader classLoader) { Assert.notNull(source, "Source instance must not be null"); Assert.hasText(typeName, "Target type name must not be null or empty"); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java index c8628861f..62f0c45f9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java @@ -25,11 +25,14 @@ import jakarta.persistence.metamodel.IdentifiableType; import jakarta.persistence.metamodel.Metamodel; import jakarta.persistence.metamodel.SingularAttribute; +import java.lang.reflect.Proxy; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; +import java.util.function.LongSupplier; +import java.util.stream.Stream; import org.eclipse.persistence.config.QueryHints; import org.eclipse.persistence.jpa.JpaQuery; @@ -38,6 +41,8 @@ import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.proxy.HibernateProxy; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.aop.support.AopUtils; import org.springframework.data.util.CloseableIterator; import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -54,22 +59,15 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @author Jens Schauder * @author Greg Turnquist * @author Yuriy Tsarkov - * @author Ariel Morelli Andres (Atlassian US, Inc.) + * @author Ariel Morelli Andres */ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment { /** * Hibernate persistence provider. - *
- * Since Hibernate 4.3 the location of the HibernateEntityManager moved to the org.hibernate.jpa package. In order to
- * support both locations we interpret both classnames as a Hibernate {@code PersistenceProvider}.
- *
- * @see DATAJPA-444
*/
- HIBERNATE(//
- Collections.singletonList(HIBERNATE_ENTITY_MANAGER_FACTORY_INTERFACE), //
- Collections.singletonList(HIBERNATE_ENTITY_MANAGER_INTERFACE), //
- Collections.singletonList(HIBERNATE_JPA_METAMODEL_TYPE)) {
+ HIBERNATE(List.of(HIBERNATE_ENTITY_MANAGER_FACTORY_INTERFACE), //
+ List.of(HIBERNATE_JPA_METAMODEL_TYPE)) {
@Override
public String extractQueryString(Query query) {
@@ -117,9 +115,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
/**
* EclipseLink persistence provider.
*/
- ECLIPSELINK(List.of(ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE1, ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE2),
- Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE),
- Collections.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
+ ECLIPSELINK(List.of(ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE), List.of(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
@Override
public String extractQueryString(Query query) {
@@ -157,8 +153,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
/**
* Unknown special provider. Use standard JPA.
*/
- GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE),
- Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.emptySet()) {
+ GENERIC_JPA(List.of(GENERIC_JPA_ENTITY_MANAGER_FACTORY_INTERFACE), Collections.emptySet()) {
@Nullable
@Override
@@ -205,8 +200,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
private static final Collection
+ * This method avoids {@link EntityManager} initialization when using
+ * {@link org.springframework.orm.jpa.SharedEntityManagerCreator} by accessing
+ * {@link EntityManager#getEntityManagerFactory()}.
*
* @param em must not be {@literal null}.
* @return will never be {@literal null}.
+ * @see org.springframework.orm.jpa.SharedEntityManagerCreator
*/
public static PersistenceProvider fromEntityManager(EntityManager em) {
Assert.notNull(em, "EntityManager must not be null");
- Class> entityManagerType = em.getDelegate().getClass();
- PersistenceProvider cachedProvider = CACHE.get(entityManagerType);
-
- if (cachedProvider != null) {
- return cachedProvider;
- }
-
- for (PersistenceProvider provider : ALL) {
- for (String entityManagerClassName : provider.entityManagerClassNames) {
- if (isEntityManagerOfType(em, entityManagerClassName)) {
- return cacheAndReturn(entityManagerType, provider);
- }
- }
- }
-
- return cacheAndReturn(entityManagerType, GENERIC_JPA);
+ return fromEntityManagerFactory(em.getEntityManagerFactory());
}
/**
@@ -296,12 +259,24 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
*
* @param emf must not be {@literal null}.
* @return will never be {@literal null}.
+ * @since 3.5.1
*/
public static PersistenceProvider fromEntityManagerFactory(EntityManagerFactory emf) {
Assert.notNull(emf, "EntityManagerFactory must not be null");
- Class> entityManagerType = emf.getPersistenceUnitUtil().getClass();
+ EntityManagerFactory unwrapped = emf;
+
+ while (Proxy.isProxyClass(unwrapped.getClass()) || AopUtils.isAopProxy(unwrapped)) {
+
+ if (Proxy.isProxyClass(unwrapped.getClass())) {
+ unwrapped = unwrapped.unwrap(null);
+ } else if (AopUtils.isAopProxy(unwrapped)) {
+ unwrapped = (EntityManagerFactory) AopProxyUtils.getSingletonTarget(unwrapped);
+ }
+ }
+
+ Class> entityManagerType = unwrapped.getClass();
PersistenceProvider cachedProvider = CACHE.get(entityManagerType);
if (cachedProvider != null) {
@@ -310,8 +285,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
for (PersistenceProvider provider : ALL) {
for (String emfClassName : provider.entityManagerFactoryClassNames) {
- if (isOfType(emf.getPersistenceUnitUtil(), emfClassName,
- emf.getPersistenceUnitUtil().getClass().getClassLoader())) {
+ if (isOfType(unwrapped, emfClassName, unwrapped.getClass().getClassLoader())) {
return cacheAndReturn(entityManagerType, provider);
}
}
@@ -408,16 +382,14 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
String GENERIC_JPA_ENTITY_MANAGER_FACTORY_INTERFACE = "jakarta.persistence.EntityManagerFactory";
String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "jakarta.persistence.EntityManager";
- String ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE1 = "org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate";
- String ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE2 = "org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl";
+ String ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManagerFactory";
String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
+ String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl";
// needed as Spring only exposes that interface via the EM proxy
- String HIBERNATE_ENTITY_MANAGER_FACTORY_INTERFACE = "org.hibernate.jpa.internal.PersistenceUnitUtilImpl";
- String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.engine.spi.SessionImplementor";
-
+ String HIBERNATE_ENTITY_MANAGER_FACTORY_INTERFACE = "org.hibernate.SessionFactory";
+ String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.Session";
String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.model.domain.JpaMetamodel";
- String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl";
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java
index ba7c3abed..66d55e239 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java
@@ -16,18 +16,20 @@
package org.springframework.data.jpa.provider;
import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
import static org.springframework.data.jpa.provider.PersistenceProvider.*;
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityManagerFactory;
import java.util.Arrays;
import java.util.Map;
-import org.assertj.core.api.Assumptions;
-import org.hibernate.Version;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.Mockito;
import org.springframework.asm.ClassWriter;
@@ -42,6 +44,7 @@ import org.springframework.util.ClassUtils;
* @author Thomas Darimont
* @author Oliver Gierke
* @author Jens Schauder
+ * @author Mark Paluch
*/
class PersistenceProviderUnitTests {
@@ -56,12 +59,32 @@ class PersistenceProviderUnitTests {
this.shadowingClassLoader = new ShadowingClassLoader(getClass().getClassLoader());
}
+ @ParameterizedTest // GH-3425
+ @EnumSource(PersistenceProvider.class)
+ void entityManagerFactoryClassNamesAreInterfaces(PersistenceProvider provider) throws ClassNotFoundException {
+
+ for (String className : provider.entityManagerFactoryClassNames) {
+ assertThat(ClassUtils.forName(className, PersistenceProvider.class.getClassLoader()).isInterface()).isTrue();
+ }
+ }
+
+ @ParameterizedTest // GH-3425
+ @EnumSource(PersistenceProvider.class)
+ void metaModelNamesExist(PersistenceProvider provider) throws ClassNotFoundException {
+
+ for (String className : provider.entityManagerFactoryClassNames) {
+ assertThat(ClassUtils.forName(className, PersistenceProvider.class.getClassLoader()).isInterface()).isNotNull();
+ }
+ }
+
@Test
void detectsEclipseLinkPersistenceProvider() throws Exception {
shadowingClassLoader.excludePackage("org.eclipse.persistence.jpa");
EntityManager em = mockProviderSpecificEntityManagerInterface(ECLIPSELINK_ENTITY_MANAGER_INTERFACE);
+ when(em.getEntityManagerFactory())
+ .thenReturn(mockProviderSpecificEntityManagerFactoryInterface(ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE));
assertThat(fromEntityManager(em)).isEqualTo(ECLIPSELINK);
}
@@ -70,31 +93,19 @@ class PersistenceProviderUnitTests {
void fallbackToGenericJpaForUnknownPersistenceProvider() throws Exception {
EntityManager em = mockProviderSpecificEntityManagerInterface("foo.bar.unknown.jpa.JpaEntityManager");
+ when(em.getEntityManagerFactory()).thenReturn(mock(EntityManagerFactory.class));
assertThat(fromEntityManager(em)).isEqualTo(GENERIC_JPA);
}
- @Test // DATAJPA-1019
- void detectsHibernatePersistenceProviderForHibernateVersion52() throws Exception {
-
- Assumptions.assumeThat(Version.getVersionString()).startsWith("5.2");
-
- shadowingClassLoader.excludePackage("org.hibernate");
-
- EntityManager em = mockProviderSpecificEntityManagerInterface(HIBERNATE_ENTITY_MANAGER_INTERFACE);
-
- assertThat(fromEntityManager(em)).isEqualTo(HIBERNATE);
- }
-
@Test // DATAJPA-1379
void detectsProviderFromProxiedEntityManager() throws Exception {
shadowingClassLoader.excludePackage("org.eclipse.persistence.jpa");
- EntityManager em = mockProviderSpecificEntityManagerInterface(ECLIPSELINK_ENTITY_MANAGER_INTERFACE);
-
EntityManager emProxy = Mockito.mock(EntityManager.class);
- Mockito.when(emProxy.getDelegate()).thenReturn(em);
+ when(emProxy.getEntityManagerFactory())
+ .thenReturn(mockProviderSpecificEntityManagerFactoryInterface(ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE));
assertThat(fromEntityManager(emProxy)).isEqualTo(ECLIPSELINK);
}
@@ -105,13 +116,23 @@ class PersistenceProviderUnitTests {
EntityManager.class);
EntityManager em = (EntityManager) Mockito.mock(providerSpecificEntityManagerInterface);
- Mockito.when(em.getDelegate()).thenReturn(em); // delegate is used to determine the classloader of the provider
- // specific interface, therefore we return the proxied
- // EntityManager.
+
+ // delegate is used to determine the classloader of the provider
+ // specific interface, therefore we return the proxied EntityManager
+ when(em.getDelegate()).thenReturn(em);
return em;
}
+ private EntityManagerFactory mockProviderSpecificEntityManagerFactoryInterface(String interfaceName)
+ throws ClassNotFoundException {
+
+ Class> providerSpecificEntityManagerInterface = InterfaceGenerator.generate(interfaceName, shadowingClassLoader,
+ EntityManager.class);
+
+ return (EntityManagerFactory) Mockito.mock(providerSpecificEntityManagerInterface);
+ }
+
static class InterfaceGenerator implements Opcodes {
static Class> generate(final String interfaceName, ClassLoader parentClassLoader, final Class>... interfaces)
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateCurrentTenantIdentifierResolver.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateCurrentTenantIdentifierResolver.java
index 436e99fb3..d3b87cb00 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateCurrentTenantIdentifierResolver.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateCurrentTenantIdentifierResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2025 the original author or authors.
+ * Copyright 2025 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.
@@ -18,21 +18,21 @@ package org.springframework.data.jpa.repository;
import java.util.Optional;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
-import org.jspecify.annotations.Nullable;
/**
- * {@code CurrentTenantIdentifierResolver} instance for testing
+ * {@code CurrentTenantIdentifierResolver} instance for testing.
*
- * @author Ariel Morelli Andres (Atlassian US, Inc.)
+ * @author Ariel Morelli Andres
*/
public class HibernateCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver