mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Runtime compatibility with JPA 4.0 M4
Closes gh-36784
This commit is contained in:
+6
-6
@@ -509,6 +509,7 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
* Delegate an incoming invocation from the proxy, dispatching to EntityManagerFactoryInfo
|
||||
* or the native EntityManagerFactory accordingly.
|
||||
*/
|
||||
@SuppressWarnings("NullAway") // for query.unwrap(null)
|
||||
Object invokeProxyMethod(Method method, Object @Nullable [] args) throws Throwable {
|
||||
if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
|
||||
return method.invoke(this, args);
|
||||
@@ -517,9 +518,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
args[0] == SynchronizationType.SYNCHRONIZED) {
|
||||
// JPA 2.1's createEntityManager(SynchronizationType, Map)
|
||||
// Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
|
||||
EntityManager rawEntityManager = (args.length > 1 ?
|
||||
getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
|
||||
getNativeEntityManagerFactory().createEntityManager());
|
||||
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(
|
||||
getNativeEntityManagerFactory(), (args.length > 1 ? (Map<?, ?>) args[1] : null));
|
||||
postProcessEntityManager(rawEntityManager);
|
||||
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
|
||||
}
|
||||
@@ -532,6 +532,7 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
// Assumably a Spring-generated proxy from SharedEntityManagerCreator:
|
||||
// since we're passing it back to the native EntityManagerFactory,
|
||||
// let's unwrap it to the original Query object from the provider.
|
||||
// Note that null is not an officially supported argument in JPA.
|
||||
try {
|
||||
args[i] = query.unwrap(null);
|
||||
}
|
||||
@@ -605,9 +606,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
|
||||
@Override
|
||||
public EntityManager createNativeEntityManager(@Nullable Map<?, ?> properties) {
|
||||
EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
|
||||
getNativeEntityManagerFactory().createEntityManager(properties) :
|
||||
getNativeEntityManagerFactory().createEntityManager());
|
||||
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(
|
||||
getNativeEntityManagerFactory(), properties);
|
||||
postProcessEntityManager(rawEntityManager);
|
||||
return rawEntityManager;
|
||||
}
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ public abstract class EntityManagerFactoryAccessor implements BeanFactoryAware {
|
||||
protected EntityManager createEntityManager() throws IllegalStateException {
|
||||
EntityManagerFactory emf = obtainEntityManagerFactory();
|
||||
Map<String, Object> properties = getJpaPropertyMap();
|
||||
return (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
|
||||
return EntityManagerFactoryUtils.createEntityManager(emf, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+26
-8
@@ -79,10 +79,10 @@ public abstract class EntityManagerFactoryUtils {
|
||||
public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER =
|
||||
DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
|
||||
|
||||
private static final @Nullable Method CREATE_ENTITY_AGENT_METHOD =
|
||||
ClassUtils.getMethodIfAvailable(EntityManagerFactory.class, "createEntityAgent");
|
||||
private static final boolean CREATE_ENTITY_MANAGER_WITHOUT_ARGUMENTS_AVAILABLE =
|
||||
ClassUtils.hasMethod(EntityManagerFactory.class, "createEntityManager");
|
||||
|
||||
private static final @Nullable Method CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD =
|
||||
private static final @Nullable Method CREATE_ENTITY_AGENT_METHOD =
|
||||
ClassUtils.getMethodIfAvailable(EntityManagerFactory.class, "createEntityAgent", Map.class);
|
||||
|
||||
private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class);
|
||||
@@ -268,7 +268,7 @@ public abstract class EntityManagerFactoryUtils {
|
||||
}
|
||||
}
|
||||
if (em == null) {
|
||||
em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
|
||||
em = createEntityManager(emf, properties);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -358,6 +358,23 @@ public abstract class EntityManagerFactoryUtils {
|
||||
return entityAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JPA EntityManager via reflectively detected JPA 3.2/4.0 API.
|
||||
* @param emf the EntityManagerFactory to create the EntityManager with
|
||||
* @param properties the properties to be passed into the {@code createEntityManager}
|
||||
* call (may be {@code null})
|
||||
* @since 7.0.8
|
||||
*/
|
||||
public static EntityManager createEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties) {
|
||||
if (CollectionUtils.isEmpty(properties)) {
|
||||
properties = null;
|
||||
}
|
||||
if (properties == null && CREATE_ENTITY_MANAGER_WITHOUT_ARGUMENTS_AVAILABLE) {
|
||||
return emf.createEntityManager(); // on JPA 3.2
|
||||
}
|
||||
return emf.createEntityManager(properties); // on JPA 4.0 even for empty properties
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JPA EntityAgent via reflectively detected JPA 4.0 API.
|
||||
* @param emf the EntityManagerFactory to create the EntityAgent with
|
||||
@@ -366,12 +383,13 @@ public abstract class EntityManagerFactoryUtils {
|
||||
* @since 7.0.4
|
||||
*/
|
||||
static Object createEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties) {
|
||||
if (CREATE_ENTITY_AGENT_METHOD == null || CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD == null) {
|
||||
if (CREATE_ENTITY_AGENT_METHOD == null) {
|
||||
throw new IllegalStateException("JPA 4.0 createEntityAgent API not available");
|
||||
}
|
||||
Object entityAgent = (!CollectionUtils.isEmpty(properties) ?
|
||||
ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD, emf, properties) :
|
||||
ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_METHOD, emf));
|
||||
if (CollectionUtils.isEmpty(properties)) {
|
||||
properties = null;
|
||||
}
|
||||
Object entityAgent = ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_METHOD, emf, properties);
|
||||
if (entityAgent == null) {
|
||||
throw new IllegalStateException("JPA 4.0 createEntityAgent API returned null");
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.util.ClassUtils;
|
||||
* {@link AbstractEntityManagerFactoryBean} and {@link SharedEntityManagerCreator} are registered.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.0
|
||||
*/
|
||||
class EntityManagerRuntimeHints implements RuntimeHintsRegistrar {
|
||||
@@ -45,6 +46,12 @@ class EntityManagerRuntimeHints implements RuntimeHintsRegistrar {
|
||||
// As of Hibernate 7.1
|
||||
private static final String SQM_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.sqm.internal.SqmQueryImpl";
|
||||
|
||||
// As of Hibernate 8.0
|
||||
private static final String SELECTION_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.internal.SelectionQueryImpl";
|
||||
|
||||
// As of Hibernate 8.0
|
||||
private static final String MUTATION_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.internal.MutationQueryImpl";
|
||||
|
||||
private static final String NATIVE_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.sql.internal.NativeQueryImpl";
|
||||
|
||||
|
||||
@@ -76,6 +83,18 @@ class EntityManagerRuntimeHints implements RuntimeHintsRegistrar {
|
||||
}
|
||||
catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(SELECTION_QUERY_IMPL_CLASS_NAME, classLoader);
|
||||
hints.proxies().registerJdkProxy(ClassUtils.getAllInterfacesForClass(clazz, classLoader));
|
||||
}
|
||||
catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(MUTATION_QUERY_IMPL_CLASS_NAME, classLoader);
|
||||
hints.proxies().registerJdkProxy(ClassUtils.getAllInterfacesForClass(clazz, classLoader));
|
||||
}
|
||||
catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(NATIVE_QUERY_IMPL_CLASS_NAME, classLoader);
|
||||
hints.proxies().registerJdkProxy(ClassUtils.getAllInterfacesForClass(clazz, classLoader));
|
||||
|
||||
+1
-3
@@ -42,7 +42,6 @@ import org.springframework.transaction.support.ResourceHolderSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
@@ -174,8 +173,7 @@ public abstract class ExtendedEntityManagerCreator {
|
||||
return createProxy(rawEntityManager, emfInfo, true, synchronizedWithTransaction);
|
||||
}
|
||||
else {
|
||||
EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
|
||||
emf.createEntityManager(properties) : emf.createEntityManager());
|
||||
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(emf, properties);
|
||||
return createProxy(rawEntityManager, null, null, null, null, true, synchronizedWithTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,8 +475,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager
|
||||
em = emfInfo.createNativeEntityManager(properties);
|
||||
}
|
||||
else {
|
||||
em = (!CollectionUtils.isEmpty(properties) ?
|
||||
emf.createEntityManager(properties) : emf.createEntityManager());
|
||||
em = EntityManagerFactoryUtils.createEntityManager(emf, properties);
|
||||
}
|
||||
if (this.entityManagerInitializer != null) {
|
||||
this.entityManagerInitializer.accept(em);
|
||||
|
||||
+9
-5
@@ -143,9 +143,7 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
*/
|
||||
public PersistenceConfiguration getPersistenceConfiguration() {
|
||||
if (this.configuration == null) {
|
||||
String name = getPersistenceUnitName();
|
||||
Assert.state(name != null, "No persistenceUnitName set");
|
||||
this.configuration = new PersistenceConfiguration(name);
|
||||
this.configuration = new PersistenceConfiguration(obtainPersistenceUnitName());
|
||||
}
|
||||
return this.configuration;
|
||||
}
|
||||
@@ -167,6 +165,12 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
super.setPersistenceUnitName(persistenceUnitName);
|
||||
}
|
||||
|
||||
private String obtainPersistenceUnitName() {
|
||||
String name = getPersistenceUnitName();
|
||||
Assert.state(name != null, "No persistenceUnitName set");
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to use Spring-based scanning for entity classes in the classpath
|
||||
* instead of using JPA's standard scanning of jar files with {@code persistence.xml}
|
||||
@@ -265,7 +269,7 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
// Create EntityManagerFactory directly through PersistenceProvider.
|
||||
EntityManagerFactory emf = (this.configuration != null ?
|
||||
provider.createEntityManagerFactory(this.configuration) :
|
||||
provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()));
|
||||
provider.createEntityManagerFactory(obtainPersistenceUnitName(), getJpaPropertyMap()));
|
||||
if (emf == null) {
|
||||
throw new PersistenceException(
|
||||
"PersistenceProvider [" + provider + "] could not find persistence unit for name '" +
|
||||
@@ -277,7 +281,7 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
// Let JPA perform its standard PersistenceProvider autodetection.
|
||||
return (this.configuration != null ?
|
||||
Persistence.createEntityManagerFactory(this.configuration) :
|
||||
Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()));
|
||||
Persistence.createEntityManagerFactory(obtainPersistenceUnitName(), getJpaPropertyMap()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -39,7 +39,6 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
@@ -376,9 +375,7 @@ public abstract class SharedEntityManagerCreator {
|
||||
boolean newTarget = false;
|
||||
if (target == null) {
|
||||
logger.debug("Creating new EntityManager for shared EntityManager invocation");
|
||||
target = (!CollectionUtils.isEmpty(this.properties) ?
|
||||
this.targetFactory.createEntityManager(this.properties) :
|
||||
this.targetFactory.createEntityManager());
|
||||
target = EntityManagerFactoryUtils.createEntityManager(this.targetFactory, this.properties);
|
||||
newTarget = true;
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -146,7 +146,11 @@ public final class PersistenceManagedTypesScanner {
|
||||
try {
|
||||
MetadataReader reader = factory.getMetadataReader(resource);
|
||||
String className = reader.getClassMetadata().getClassName();
|
||||
if (matchesEntityTypeFilter(reader, factory) && this.managedClassNameFilter.matches(className)) {
|
||||
if (className.endsWith(ClassUtils.PACKAGE_INFO_SUFFIX)) {
|
||||
scanResult.managedPackages.add(className.substring(0,
|
||||
className.length() - ClassUtils.PACKAGE_INFO_SUFFIX.length()));
|
||||
}
|
||||
else if (matchesEntityTypeFilter(reader, factory) && this.managedClassNameFilter.matches(className)) {
|
||||
scanResult.managedClassNames.add(className);
|
||||
if (scanResult.persistenceUnitRootUrl == null) {
|
||||
URL url = resource.getURL();
|
||||
@@ -155,10 +159,6 @@ public final class PersistenceManagedTypesScanner {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (className.endsWith(ClassUtils.PACKAGE_INFO_SUFFIX)) {
|
||||
scanResult.managedPackages.add(className.substring(0,
|
||||
className.length() - ClassUtils.PACKAGE_INFO_SUFFIX.length()));
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
// Ignore non-readable resource
|
||||
|
||||
+1
-1
@@ -240,7 +240,7 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
|
||||
* @see jakarta.persistence.EntityManagerFactory#createEntityManager()
|
||||
*/
|
||||
protected EntityManager createEntityManager(EntityManagerFactory emf) {
|
||||
return emf.createEntityManager();
|
||||
return EntityManagerFactoryUtils.createEntityManager(emf, null);
|
||||
}
|
||||
|
||||
private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) {
|
||||
|
||||
+1
-16
@@ -19,11 +19,6 @@ package org.springframework.orm.jpa;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.query.CommonQueryContract;
|
||||
import org.hibernate.query.SelectionQuery;
|
||||
import org.hibernate.query.hql.spi.SqmQueryImplementor;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.sqm.spi.InterpretationsKeySource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -47,8 +42,7 @@ class EntityManagerRuntimeHintsTests {
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
AotServices.factories().load(RuntimeHintsRegistrar.class)
|
||||
.forEach(registrar -> registrar.registerHints(this.hints,
|
||||
ClassUtils.getDefaultClassLoader()));
|
||||
.forEach(registrar -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -69,13 +63,4 @@ class EntityManagerRuntimeHintsTests {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(EntityManagerFactory.class, "getMetamodel")).accepts(this.hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sqmQueryHints() {
|
||||
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(
|
||||
SqmQueryImplementor.class,
|
||||
InterpretationsKeySource.class,
|
||||
DomainQueryExecutionContext.class,
|
||||
SelectionQuery.class,
|
||||
CommonQueryContract.class)).accepts(this.hints);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -76,19 +76,20 @@ class OpenEntityManagerInViewTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setup() {
|
||||
given(factory.createEntityManager()).willReturn(manager);
|
||||
this.request.setAsyncSupported(true);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
void cleanup() {
|
||||
assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty();
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
|
||||
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();
|
||||
assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void openEntityManagerInViewInterceptor() {
|
||||
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ class PersistenceContextTransactionTests {
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clear() {
|
||||
void cleanup() {
|
||||
assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty();
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
|
||||
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();
|
||||
|
||||
Reference in New Issue
Block a user