Merge branch '7.0.x'

This commit is contained in:
Juergen Hoeller
2026-05-29 13:31:01 +02:00
14 changed files with 127 additions and 84 deletions
@@ -1075,25 +1075,25 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
* if not recovered yet, and at debug level if already recovered.
* Can be overridden in subclasses.
* @param ex the exception to handle
* @param alreadyRecovered whether a previously executing listener
* already recovered from the present listener setup failure
* (this usually indicates a follow-up failure than can be ignored
* other than for debug log purposes)
* @param alreadyHandled whether a previously executing listener already
* recovered from the present listener setup failure or the problem has
* been handled otherwise already (this usually indicates a follow-up
* failure than can be ignored other than for debug log purposes)
* @see #recoverAfterListenerSetupFailure()
*/
protected void handleListenerSetupFailure(Throwable ex, boolean alreadyRecovered) {
protected void handleListenerSetupFailure(Throwable ex, boolean alreadyHandled) {
if (ex instanceof JMSException jmsException) {
invokeExceptionListener(jmsException);
}
if (ex instanceof SharedConnectionNotInitializedException) {
if (!alreadyRecovered) {
if (!alreadyHandled) {
logger.debug("JMS message listener invoker needs to establish shared Connection");
}
}
else {
// Recovery during active operation...
if (alreadyRecovered) {
logger.debug("Setup of JMS message listener invoker failed - already recovered by other invoker", ex);
if (alreadyHandled) {
logger.debug("Setup of JMS message listener invoker failed", ex);
}
else {
StringBuilder msg = new StringBuilder();
@@ -1317,6 +1317,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
lifecycleLock.unlock();
}
boolean messageReceived = false;
boolean exhausted = false;
try {
// For core consumers without maxMessagesPerTask, no idle limit applies since they
// will always get rescheduled immediately anyway. Whereas for surplus consumers
@@ -1340,31 +1341,45 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
}
catch (Throwable ex) {
clearResources();
boolean alreadyHandled = false;
if (this.invokerBackOff != null) {
// We failed more than once in a row, probably due to a specific failure
// from the JMS receive call even after a successful connection recovery:
// locally wait before a further recovery attempt to avoid a burst retry.
applyBackOffTime(this.invokerBackOff);
alreadyHandled = true;
if (logger.isWarnEnabled()) {
logger.warn("Recurring setup failure in " + id() + " - retrying using " +
this.invokerBackOff + ". Cause: " + (ex instanceof JMSException jmsException ?
JmsUtils.buildExceptionMessage(jmsException) : ex.getMessage()));
}
exhausted = !applyBackOffTime(this.invokerBackOff);
if (exhausted) {
// Too many recurring setup failures at the invoker level.
if (logger.isWarnEnabled()) {
logger.warn("Exhausted back-off in " + id() + " after recurring setup failure: " +
this.invokerBackOff);
}
}
}
else {
this.invokerBackOff = DefaultMessageListenerContainer.this.backOff.start();
}
boolean alreadyRecovered = false;
recoveryLock.lock();
try {
if (this.lastRecoveryMarker == currentRecoveryMarker) {
handleListenerSetupFailure(ex, false);
recoverAfterListenerSetupFailure();
currentRecoveryMarker = new Object();
boolean recovered = false;
if (!exhausted) {
recoveryLock.lock();
try {
if (this.lastRecoveryMarker == DefaultMessageListenerContainer.this.currentRecoveryMarker) {
handleListenerSetupFailure(ex, alreadyHandled);
recoverAfterListenerSetupFailure();
DefaultMessageListenerContainer.this.currentRecoveryMarker = new Object();
recovered = true;
}
}
else {
alreadyRecovered = true;
finally {
recoveryLock.unlock();
}
}
finally {
recoveryLock.unlock();
}
if (alreadyRecovered) {
if (!recovered) {
handleListenerSetupFailure(ex, true);
}
}
@@ -1385,7 +1400,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
}
lifecycleLock.lock();
try {
if (!shouldRescheduleInvoker(this.idleTaskExecutionCount) || !rescheduleTaskIfNecessary(this)) {
if (exhausted || !shouldRescheduleInvoker(this.idleTaskExecutionCount) ||
!rescheduleTaskIfNecessary(this)) {
// We're shutting down completely.
scheduledInvokers.remove(this);
if (logger.isDebugEnabled()) {
@@ -1394,16 +1410,15 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
lifecycleCondition.signalAll();
clearResources();
}
else if (isRunning()) {
if (isRunning()) {
int nonPausedConsumers = getScheduledConsumerCount() - getPausedTaskCount();
if (nonPausedConsumers < 1) {
logger.error("All scheduled consumers have been paused, probably due to tasks having been rejected. " +
"Check your thread pool configuration! Manual recovery necessary through a start() call.");
logger.error("All scheduled consumers have been paused due to tasks having been exhausted/rejected. " +
"Check your back-off and executor setup! Manual recovery necessary through a start() call.");
}
else if (nonPausedConsumers < getConcurrentConsumers()) {
logger.warn("Number of scheduled consumers has dropped below concurrentConsumers limit, probably " +
"due to tasks having been rejected. Check your thread pool configuration! Automatic recovery " +
"to be triggered by remaining consumers.");
logger.warn("Number of scheduled consumers has dropped below concurrentConsumers limit due to tasks " +
"having been exhausted/rejected. Automatic recovery to be triggered by remaining consumers.");
}
}
}
@@ -1461,7 +1476,10 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
try {
initResourcesIfNecessary();
boolean messageReceived = receiveAndExecute(this, this.session, this.consumer);
this.invokerBackOff = null; // successful receive attempt without exception
if (this.invokerBackOff != null) {
logger.debug("Successful receive attempt after JMS message listener invoker recovery");
this.invokerBackOff = null;
}
return messageReceived;
}
finally {
@@ -1552,6 +1570,10 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
this.session = null;
}
private String id() {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
}
@Override
public boolean isLongLived() {
return (maxMessagesPerTask < 0);
@@ -513,6 +513,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);
@@ -521,9 +522,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);
}
@@ -536,6 +536,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);
}
@@ -609,9 +610,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;
}
@@ -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);
}
/**
@@ -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));
@@ -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);
@@ -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()));
}
}
@@ -40,7 +40,6 @@ import org.jspecify.annotations.Nullable;
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;
/**
@@ -396,9 +395,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;
}
@@ -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
@@ -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) {
@@ -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);
}
}
@@ -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();
@@ -74,7 +74,7 @@ class PersistenceContextTransactionTests {
}
@AfterEach
void clear() {
void cleanup() {
assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty();
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();