Skip to content

Commit 175c1f9

Browse files
committed
Runtime compatibility with JPA 4.0 M4
Closes gh-36784
1 parent 0b77c3d commit 175c1f9

13 files changed

Lines changed: 75 additions & 54 deletions

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ else if (emf != null) {
509509
* Delegate an incoming invocation from the proxy, dispatching to EntityManagerFactoryInfo
510510
* or the native EntityManagerFactory accordingly.
511511
*/
512+
@SuppressWarnings("NullAway") // for query.unwrap(null)
512513
Object invokeProxyMethod(Method method, Object @Nullable [] args) throws Throwable {
513514
if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
514515
return method.invoke(this, args);
@@ -517,9 +518,8 @@ else if (method.getName().equals("createEntityManager") && args != null && args.
517518
args[0] == SynchronizationType.SYNCHRONIZED) {
518519
// JPA 2.1's createEntityManager(SynchronizationType, Map)
519520
// Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
520-
EntityManager rawEntityManager = (args.length > 1 ?
521-
getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
522-
getNativeEntityManagerFactory().createEntityManager());
521+
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(
522+
getNativeEntityManagerFactory(), (args.length > 1 ? (Map<?, ?>) args[1] : null));
523523
postProcessEntityManager(rawEntityManager);
524524
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
525525
}
@@ -532,6 +532,7 @@ else if (method.getName().equals("createEntityManager") && args != null && args.
532532
// Assumably a Spring-generated proxy from SharedEntityManagerCreator:
533533
// since we're passing it back to the native EntityManagerFactory,
534534
// let's unwrap it to the original Query object from the provider.
535+
// Note that null is not an officially supported argument in JPA.
535536
try {
536537
args[i] = query.unwrap(null);
537538
}
@@ -605,9 +606,8 @@ public EntityManagerFactory getNativeEntityManagerFactory() {
605606

606607
@Override
607608
public EntityManager createNativeEntityManager(@Nullable Map<?, ?> properties) {
608-
EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
609-
getNativeEntityManagerFactory().createEntityManager(properties) :
610-
getNativeEntityManagerFactory().createEntityManager());
609+
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(
610+
getNativeEntityManagerFactory(), properties);
611611
postProcessEntityManager(rawEntityManager);
612612
return rawEntityManager;
613613
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
162162
protected EntityManager createEntityManager() throws IllegalStateException {
163163
EntityManagerFactory emf = obtainEntityManagerFactory();
164164
Map<String, Object> properties = getJpaPropertyMap();
165-
return (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
165+
return EntityManagerFactoryUtils.createEntityManager(emf, properties);
166166
}
167167

168168
/**

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public abstract class EntityManagerFactoryUtils {
7979
public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER =
8080
DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
8181

82-
private static final @Nullable Method CREATE_ENTITY_AGENT_METHOD =
83-
ClassUtils.getMethodIfAvailable(EntityManagerFactory.class, "createEntityAgent");
82+
private static final boolean CREATE_ENTITY_MANAGER_WITHOUT_ARGUMENTS_AVAILABLE =
83+
ClassUtils.hasMethod(EntityManagerFactory.class, "createEntityManager");
8484

85-
private static final @Nullable Method CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD =
85+
private static final @Nullable Method CREATE_ENTITY_AGENT_METHOD =
8686
ClassUtils.getMethodIfAvailable(EntityManagerFactory.class, "createEntityAgent", Map.class);
8787

8888
private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class);
@@ -268,7 +268,7 @@ else if (!TransactionSynchronizationManager.isSynchronizationActive()) {
268268
}
269269
}
270270
if (em == null) {
271-
em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
271+
em = createEntityManager(emf, properties);
272272
}
273273

274274
try {
@@ -358,6 +358,23 @@ else if (!TransactionSynchronizationManager.isSynchronizationActive()) {
358358
return entityAgent;
359359
}
360360

361+
/**
362+
* Create a new JPA EntityManager via reflectively detected JPA 3.2/4.0 API.
363+
* @param emf the EntityManagerFactory to create the EntityManager with
364+
* @param properties the properties to be passed into the {@code createEntityManager}
365+
* call (may be {@code null})
366+
* @since 7.0.8
367+
*/
368+
public static EntityManager createEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties) {
369+
if (CollectionUtils.isEmpty(properties)) {
370+
properties = null;
371+
}
372+
if (properties == null && CREATE_ENTITY_MANAGER_WITHOUT_ARGUMENTS_AVAILABLE) {
373+
return emf.createEntityManager(); // on JPA 3.2
374+
}
375+
return emf.createEntityManager(properties); // on JPA 4.0 even for empty properties
376+
}
377+
361378
/**
362379
* Create a new JPA EntityAgent via reflectively detected JPA 4.0 API.
363380
* @param emf the EntityManagerFactory to create the EntityAgent with
@@ -366,12 +383,13 @@ else if (!TransactionSynchronizationManager.isSynchronizationActive()) {
366383
* @since 7.0.4
367384
*/
368385
static Object createEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties) {
369-
if (CREATE_ENTITY_AGENT_METHOD == null || CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD == null) {
386+
if (CREATE_ENTITY_AGENT_METHOD == null) {
370387
throw new IllegalStateException("JPA 4.0 createEntityAgent API not available");
371388
}
372-
Object entityAgent = (!CollectionUtils.isEmpty(properties) ?
373-
ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD, emf, properties) :
374-
ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_METHOD, emf));
389+
if (CollectionUtils.isEmpty(properties)) {
390+
properties = null;
391+
}
392+
Object entityAgent = ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_METHOD, emf, properties);
375393
if (entityAgent == null) {
376394
throw new IllegalStateException("JPA 4.0 createEntityAgent API returned null");
377395
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* {@link AbstractEntityManagerFactoryBean} and {@link SharedEntityManagerCreator} are registered.
3232
*
3333
* @author Sebastien Deleuze
34+
* @author Juergen Hoeller
3435
* @since 6.0
3536
*/
3637
class EntityManagerRuntimeHints implements RuntimeHintsRegistrar {
@@ -45,6 +46,12 @@ class EntityManagerRuntimeHints implements RuntimeHintsRegistrar {
4546
// As of Hibernate 7.1
4647
private static final String SQM_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.sqm.internal.SqmQueryImpl";
4748

49+
// As of Hibernate 8.0
50+
private static final String SELECTION_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.internal.SelectionQueryImpl";
51+
52+
// As of Hibernate 8.0
53+
private static final String MUTATION_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.internal.MutationQueryImpl";
54+
4855
private static final String NATIVE_QUERY_IMPL_CLASS_NAME = "org.hibernate.query.sql.internal.NativeQueryImpl";
4956

5057

@@ -76,6 +83,18 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
7683
}
7784
catch (ClassNotFoundException ignored) {
7885
}
86+
try {
87+
Class<?> clazz = ClassUtils.forName(SELECTION_QUERY_IMPL_CLASS_NAME, classLoader);
88+
hints.proxies().registerJdkProxy(ClassUtils.getAllInterfacesForClass(clazz, classLoader));
89+
}
90+
catch (ClassNotFoundException ignored) {
91+
}
92+
try {
93+
Class<?> clazz = ClassUtils.forName(MUTATION_QUERY_IMPL_CLASS_NAME, classLoader);
94+
hints.proxies().registerJdkProxy(ClassUtils.getAllInterfacesForClass(clazz, classLoader));
95+
}
96+
catch (ClassNotFoundException ignored) {
97+
}
7998
try {
8099
Class<?> clazz = ClassUtils.forName(NATIVE_QUERY_IMPL_CLASS_NAME, classLoader);
81100
hints.proxies().registerJdkProxy(ClassUtils.getAllInterfacesForClass(clazz, classLoader));

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import org.springframework.transaction.support.TransactionSynchronizationManager;
4343
import org.springframework.util.Assert;
4444
import org.springframework.util.ClassUtils;
45-
import org.springframework.util.CollectionUtils;
4645
import org.springframework.util.ConcurrentReferenceHashMap;
4746

4847
/**
@@ -174,8 +173,7 @@ public static EntityManager createContainerManagedEntityManager(
174173
return createProxy(rawEntityManager, emfInfo, true, synchronizedWithTransaction);
175174
}
176175
else {
177-
EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
178-
emf.createEntityManager(properties) : emf.createEntityManager());
176+
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(emf, properties);
179177
return createProxy(rawEntityManager, null, null, null, null, true, synchronizedWithTransaction);
180178
}
181179
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,7 @@ protected EntityManager createEntityManagerForTransaction() {
475475
em = emfInfo.createNativeEntityManager(properties);
476476
}
477477
else {
478-
em = (!CollectionUtils.isEmpty(properties) ?
479-
emf.createEntityManager(properties) : emf.createEntityManager());
478+
em = EntityManagerFactoryUtils.createEntityManager(emf, properties);
480479
}
481480
if (this.entityManagerInitializer != null) {
482481
this.entityManagerInitializer.accept(em);

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,7 @@ public void setPersistenceConfiguration(PersistenceConfiguration configuration)
143143
*/
144144
public PersistenceConfiguration getPersistenceConfiguration() {
145145
if (this.configuration == null) {
146-
String name = getPersistenceUnitName();
147-
Assert.state(name != null, "No persistenceUnitName set");
148-
this.configuration = new PersistenceConfiguration(name);
146+
this.configuration = new PersistenceConfiguration(obtainPersistenceUnitName());
149147
}
150148
return this.configuration;
151149
}
@@ -167,6 +165,12 @@ public void setPersistenceUnitName(@Nullable String persistenceUnitName) {
167165
super.setPersistenceUnitName(persistenceUnitName);
168166
}
169167

168+
private String obtainPersistenceUnitName() {
169+
String name = getPersistenceUnitName();
170+
Assert.state(name != null, "No persistenceUnitName set");
171+
return name;
172+
}
173+
170174
/**
171175
* Set whether to use Spring-based scanning for entity classes in the classpath
172176
* instead of using JPA's standard scanning of jar files with {@code persistence.xml}
@@ -265,7 +269,7 @@ protected EntityManagerFactory createNativeEntityManagerFactory() throws Persist
265269
// Create EntityManagerFactory directly through PersistenceProvider.
266270
EntityManagerFactory emf = (this.configuration != null ?
267271
provider.createEntityManagerFactory(this.configuration) :
268-
provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()));
272+
provider.createEntityManagerFactory(obtainPersistenceUnitName(), getJpaPropertyMap()));
269273
if (emf == null) {
270274
throw new PersistenceException(
271275
"PersistenceProvider [" + provider + "] could not find persistence unit for name '" +
@@ -277,7 +281,7 @@ protected EntityManagerFactory createNativeEntityManagerFactory() throws Persist
277281
// Let JPA perform its standard PersistenceProvider autodetection.
278282
return (this.configuration != null ?
279283
Persistence.createEntityManagerFactory(this.configuration) :
280-
Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()));
284+
Persistence.createEntityManagerFactory(obtainPersistenceUnitName(), getJpaPropertyMap()));
281285
}
282286
}
283287

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939

4040
import org.springframework.transaction.support.TransactionSynchronizationManager;
4141
import org.springframework.util.ClassUtils;
42-
import org.springframework.util.CollectionUtils;
4342
import org.springframework.util.ConcurrentReferenceHashMap;
4443

4544
/**
@@ -376,9 +375,7 @@ public SharedEntityManagerInvocationHandler(
376375
boolean newTarget = false;
377376
if (target == null) {
378377
logger.debug("Creating new EntityManager for shared EntityManager invocation");
379-
target = (!CollectionUtils.isEmpty(this.properties) ?
380-
this.targetFactory.createEntityManager(this.properties) :
381-
this.targetFactory.createEntityManager());
378+
target = EntityManagerFactoryUtils.createEntityManager(this.targetFactory, this.properties);
382379
newTarget = true;
383380
}
384381

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ private void scanPackage(String pkg, ScanResult scanResult) {
146146
try {
147147
MetadataReader reader = factory.getMetadataReader(resource);
148148
String className = reader.getClassMetadata().getClassName();
149-
if (matchesEntityTypeFilter(reader, factory) && this.managedClassNameFilter.matches(className)) {
149+
if (className.endsWith(ClassUtils.PACKAGE_INFO_SUFFIX)) {
150+
scanResult.managedPackages.add(className.substring(0,
151+
className.length() - ClassUtils.PACKAGE_INFO_SUFFIX.length()));
152+
}
153+
else if (matchesEntityTypeFilter(reader, factory) && this.managedClassNameFilter.matches(className)) {
150154
scanResult.managedClassNames.add(className);
151155
if (scanResult.persistenceUnitRootUrl == null) {
152156
URL url = resource.getURL();
@@ -155,10 +159,6 @@ private void scanPackage(String pkg, ScanResult scanResult) {
155159
}
156160
}
157161
}
158-
if (className.endsWith(ClassUtils.PACKAGE_INFO_SUFFIX)) {
159-
scanResult.managedPackages.add(className.substring(0,
160-
className.length() - ClassUtils.PACKAGE_INFO_SUFFIX.length()));
161-
}
162162
}
163163
catch (FileNotFoundException ex) {
164164
// Ignore non-readable resource

spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ else if (!StringUtils.hasLength(puName) && wac.containsBean(DEFAULT_ENTITY_MANAG
240240
* @see jakarta.persistence.EntityManagerFactory#createEntityManager()
241241
*/
242242
protected EntityManager createEntityManager(EntityManagerFactory emf) {
243-
return emf.createEntityManager();
243+
return EntityManagerFactoryUtils.createEntityManager(emf, null);
244244
}
245245

246246
private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) {

0 commit comments

Comments
 (0)