Skip to content

Commit 2e65c1d

Browse files
committed
Merge branch '7.0.x'
2 parents ca72cd6 + 175c1f9 commit 2e65c1d

14 files changed

Lines changed: 127 additions & 84 deletions

spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,25 +1075,25 @@ protected void stopSharedConnection() {
10751075
* if not recovered yet, and at debug level if already recovered.
10761076
* Can be overridden in subclasses.
10771077
* @param ex the exception to handle
1078-
* @param alreadyRecovered whether a previously executing listener
1079-
* already recovered from the present listener setup failure
1080-
* (this usually indicates a follow-up failure than can be ignored
1081-
* other than for debug log purposes)
1078+
* @param alreadyHandled whether a previously executing listener already
1079+
* recovered from the present listener setup failure or the problem has
1080+
* been handled otherwise already (this usually indicates a follow-up
1081+
* failure than can be ignored other than for debug log purposes)
10821082
* @see #recoverAfterListenerSetupFailure()
10831083
*/
1084-
protected void handleListenerSetupFailure(Throwable ex, boolean alreadyRecovered) {
1084+
protected void handleListenerSetupFailure(Throwable ex, boolean alreadyHandled) {
10851085
if (ex instanceof JMSException jmsException) {
10861086
invokeExceptionListener(jmsException);
10871087
}
10881088
if (ex instanceof SharedConnectionNotInitializedException) {
1089-
if (!alreadyRecovered) {
1089+
if (!alreadyHandled) {
10901090
logger.debug("JMS message listener invoker needs to establish shared Connection");
10911091
}
10921092
}
10931093
else {
10941094
// Recovery during active operation...
1095-
if (alreadyRecovered) {
1096-
logger.debug("Setup of JMS message listener invoker failed - already recovered by other invoker", ex);
1095+
if (alreadyHandled) {
1096+
logger.debug("Setup of JMS message listener invoker failed", ex);
10971097
}
10981098
else {
10991099
StringBuilder msg = new StringBuilder();
@@ -1317,6 +1317,7 @@ public void run() {
13171317
lifecycleLock.unlock();
13181318
}
13191319
boolean messageReceived = false;
1320+
boolean exhausted = false;
13201321
try {
13211322
// For core consumers without maxMessagesPerTask, no idle limit applies since they
13221323
// will always get rescheduled immediately anyway. Whereas for surplus consumers
@@ -1340,31 +1341,45 @@ public void run() {
13401341
}
13411342
catch (Throwable ex) {
13421343
clearResources();
1344+
boolean alreadyHandled = false;
13431345
if (this.invokerBackOff != null) {
13441346
// We failed more than once in a row, probably due to a specific failure
13451347
// from the JMS receive call even after a successful connection recovery:
13461348
// locally wait before a further recovery attempt to avoid a burst retry.
1347-
applyBackOffTime(this.invokerBackOff);
1349+
alreadyHandled = true;
1350+
if (logger.isWarnEnabled()) {
1351+
logger.warn("Recurring setup failure in " + id() + " - retrying using " +
1352+
this.invokerBackOff + ". Cause: " + (ex instanceof JMSException jmsException ?
1353+
JmsUtils.buildExceptionMessage(jmsException) : ex.getMessage()));
1354+
}
1355+
exhausted = !applyBackOffTime(this.invokerBackOff);
1356+
if (exhausted) {
1357+
// Too many recurring setup failures at the invoker level.
1358+
if (logger.isWarnEnabled()) {
1359+
logger.warn("Exhausted back-off in " + id() + " after recurring setup failure: " +
1360+
this.invokerBackOff);
1361+
}
1362+
}
13481363
}
13491364
else {
13501365
this.invokerBackOff = DefaultMessageListenerContainer.this.backOff.start();
13511366
}
1352-
boolean alreadyRecovered = false;
1353-
recoveryLock.lock();
1354-
try {
1355-
if (this.lastRecoveryMarker == currentRecoveryMarker) {
1356-
handleListenerSetupFailure(ex, false);
1357-
recoverAfterListenerSetupFailure();
1358-
currentRecoveryMarker = new Object();
1367+
boolean recovered = false;
1368+
if (!exhausted) {
1369+
recoveryLock.lock();
1370+
try {
1371+
if (this.lastRecoveryMarker == DefaultMessageListenerContainer.this.currentRecoveryMarker) {
1372+
handleListenerSetupFailure(ex, alreadyHandled);
1373+
recoverAfterListenerSetupFailure();
1374+
DefaultMessageListenerContainer.this.currentRecoveryMarker = new Object();
1375+
recovered = true;
1376+
}
13591377
}
1360-
else {
1361-
alreadyRecovered = true;
1378+
finally {
1379+
recoveryLock.unlock();
13621380
}
13631381
}
1364-
finally {
1365-
recoveryLock.unlock();
1366-
}
1367-
if (alreadyRecovered) {
1382+
if (!recovered) {
13681383
handleListenerSetupFailure(ex, true);
13691384
}
13701385
}
@@ -1385,7 +1400,8 @@ public void run() {
13851400
}
13861401
lifecycleLock.lock();
13871402
try {
1388-
if (!shouldRescheduleInvoker(this.idleTaskExecutionCount) || !rescheduleTaskIfNecessary(this)) {
1403+
if (exhausted || !shouldRescheduleInvoker(this.idleTaskExecutionCount) ||
1404+
!rescheduleTaskIfNecessary(this)) {
13891405
// We're shutting down completely.
13901406
scheduledInvokers.remove(this);
13911407
if (logger.isDebugEnabled()) {
@@ -1394,16 +1410,15 @@ public void run() {
13941410
lifecycleCondition.signalAll();
13951411
clearResources();
13961412
}
1397-
else if (isRunning()) {
1413+
if (isRunning()) {
13981414
int nonPausedConsumers = getScheduledConsumerCount() - getPausedTaskCount();
13991415
if (nonPausedConsumers < 1) {
1400-
logger.error("All scheduled consumers have been paused, probably due to tasks having been rejected. " +
1401-
"Check your thread pool configuration! Manual recovery necessary through a start() call.");
1416+
logger.error("All scheduled consumers have been paused due to tasks having been exhausted/rejected. " +
1417+
"Check your back-off and executor setup! Manual recovery necessary through a start() call.");
14021418
}
14031419
else if (nonPausedConsumers < getConcurrentConsumers()) {
1404-
logger.warn("Number of scheduled consumers has dropped below concurrentConsumers limit, probably " +
1405-
"due to tasks having been rejected. Check your thread pool configuration! Automatic recovery " +
1406-
"to be triggered by remaining consumers.");
1420+
logger.warn("Number of scheduled consumers has dropped below concurrentConsumers limit due to tasks " +
1421+
"having been exhausted/rejected. Automatic recovery to be triggered by remaining consumers.");
14071422
}
14081423
}
14091424
}
@@ -1461,7 +1476,10 @@ private boolean invokeListener() throws JMSException {
14611476
try {
14621477
initResourcesIfNecessary();
14631478
boolean messageReceived = receiveAndExecute(this, this.session, this.consumer);
1464-
this.invokerBackOff = null; // successful receive attempt without exception
1479+
if (this.invokerBackOff != null) {
1480+
logger.debug("Successful receive attempt after JMS message listener invoker recovery");
1481+
this.invokerBackOff = null;
1482+
}
14651483
return messageReceived;
14661484
}
14671485
finally {
@@ -1552,6 +1570,10 @@ private void clearResources() {
15521570
this.session = null;
15531571
}
15541572

1573+
private String id() {
1574+
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
1575+
}
1576+
15551577
@Override
15561578
public boolean isLongLived() {
15571579
return (maxMessagesPerTask < 0);

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
@@ -513,6 +513,7 @@ else if (emf != null) {
513513
* Delegate an incoming invocation from the proxy, dispatching to EntityManagerFactoryInfo
514514
* or the native EntityManagerFactory accordingly.
515515
*/
516+
@SuppressWarnings("NullAway") // for query.unwrap(null)
516517
Object invokeProxyMethod(Method method, Object @Nullable [] args) throws Throwable {
517518
if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
518519
return method.invoke(this, args);
@@ -521,9 +522,8 @@ else if (method.getName().equals("createEntityManager") && args != null && args.
521522
args[0] == SynchronizationType.SYNCHRONIZED) {
522523
// JPA 2.1's createEntityManager(SynchronizationType, Map)
523524
// Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
524-
EntityManager rawEntityManager = (args.length > 1 ?
525-
getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
526-
getNativeEntityManagerFactory().createEntityManager());
525+
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(
526+
getNativeEntityManagerFactory(), (args.length > 1 ? (Map<?, ?>) args[1] : null));
527527
postProcessEntityManager(rawEntityManager);
528528
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
529529
}
@@ -536,6 +536,7 @@ else if (method.getName().equals("createEntityManager") && args != null && args.
536536
// Assumably a Spring-generated proxy from SharedEntityManagerCreator:
537537
// since we're passing it back to the native EntityManagerFactory,
538538
// let's unwrap it to the original Query object from the provider.
539+
// Note that null is not an officially supported argument in JPA.
539540
try {
540541
args[i] = query.unwrap(null);
541542
}
@@ -609,9 +610,8 @@ public EntityManagerFactory getNativeEntityManagerFactory() {
609610

610611
@Override
611612
public EntityManager createNativeEntityManager(@Nullable Map<?, ?> properties) {
612-
EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
613-
getNativeEntityManagerFactory().createEntityManager(properties) :
614-
getNativeEntityManagerFactory().createEntityManager());
613+
EntityManager rawEntityManager = EntityManagerFactoryUtils.createEntityManager(
614+
getNativeEntityManagerFactory(), properties);
615615
postProcessEntityManager(rawEntityManager);
616616
return rawEntityManager;
617617
}

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
@@ -40,7 +40,6 @@
4040
import org.springframework.transaction.support.TransactionSynchronizationManager;
4141
import org.springframework.util.Assert;
4242
import org.springframework.util.ClassUtils;
43-
import org.springframework.util.CollectionUtils;
4443
import org.springframework.util.ConcurrentReferenceHashMap;
4544

4645
/**
@@ -396,9 +395,7 @@ public SharedEntityManagerInvocationHandler(
396395
boolean newTarget = false;
397396
if (target == null) {
398397
logger.debug("Creating new EntityManager for shared EntityManager invocation");
399-
target = (!CollectionUtils.isEmpty(this.properties) ?
400-
this.targetFactory.createEntityManager(this.properties) :
401-
this.targetFactory.createEntityManager());
398+
target = EntityManagerFactoryUtils.createEntityManager(this.targetFactory, this.properties);
402399
newTarget = true;
403400
}
404401

0 commit comments

Comments
 (0)