Skip to content

Commit 557dd1f

Browse files
committed
initialize transaction synchronization before doBegin call in order to avoid OutOfMemory failures after resource binding
1 parent 49bf85b commit 557dd1f

File tree

2 files changed

+71
-29
lines changed

2 files changed

+71
-29
lines changed

org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -361,23 +361,31 @@ else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATIO
361361
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
362362
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
363363
SuspendedResourcesHolder suspendedResources = suspend(null);
364-
if (debugEnabled) {
365-
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
366-
}
364+
DefaultTransactionStatus status = null;
367365
try {
366+
if (debugEnabled) {
367+
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
368+
}
369+
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
370+
status = newTransactionStatus(
371+
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
368372
doBegin(transaction, definition);
373+
return status;
369374
}
370375
catch (RuntimeException ex) {
376+
if (status != null && status.isNewSynchronization()) {
377+
TransactionSynchronizationManager.clear();
378+
}
371379
resume(null, suspendedResources);
372380
throw ex;
373381
}
374382
catch (Error err) {
383+
if (status != null && status.isNewSynchronization()) {
384+
TransactionSynchronizationManager.clear();
385+
}
375386
resume(null, suspendedResources);
376387
throw err;
377388
}
378-
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
379-
return newTransactionStatus(
380-
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
381389
}
382390
else {
383391
// Create "empty" transaction: no actual transaction, but potentially synchronization.
@@ -414,20 +422,28 @@ private TransactionStatus handleExistingTransaction(
414422
definition.getName() + "]");
415423
}
416424
SuspendedResourcesHolder suspendedResources = suspend(transaction);
425+
DefaultTransactionStatus status = null;
417426
try {
427+
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
428+
status = newTransactionStatus(
429+
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
418430
doBegin(transaction, definition);
431+
return status;
419432
}
420433
catch (RuntimeException beginEx) {
434+
if (status != null && status.isNewSynchronization()) {
435+
TransactionSynchronizationManager.clear();
436+
}
421437
resumeAfterBeginException(transaction, suspendedResources, beginEx);
422438
throw beginEx;
423439
}
424440
catch (Error beginErr) {
441+
if (status != null && status.isNewSynchronization()) {
442+
TransactionSynchronizationManager.clear();
443+
}
425444
resumeAfterBeginException(transaction, suspendedResources, beginErr);
426445
throw beginErr;
427446
}
428-
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
429-
return newTransactionStatus(
430-
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
431447
}
432448

433449
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
@@ -452,9 +468,25 @@ private TransactionStatus handleExistingTransaction(
452468
// Nested transaction through nested begin and commit/rollback calls.
453469
// Usually only for JTA: Spring synchronization might get activated here
454470
// in case of a pre-existing JTA transaction.
455-
doBegin(transaction, definition);
456471
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
457-
return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
472+
DefaultTransactionStatus status = newTransactionStatus(
473+
definition, transaction, true, newSynchronization, debugEnabled, null);
474+
try {
475+
doBegin(transaction, definition);
476+
}
477+
catch (RuntimeException beginEx) {
478+
if (status != null && status.isNewSynchronization()) {
479+
TransactionSynchronizationManager.clear();
480+
}
481+
throw beginEx;
482+
}
483+
catch (Error beginErr) {
484+
if (status != null && status.isNewSynchronization()) {
485+
TransactionSynchronizationManager.clear();
486+
}
487+
throw beginErr;
488+
}
489+
return status;
458490
}
459491
}
460492

@@ -495,18 +527,27 @@ protected DefaultTransactionStatus newTransactionStatus(
495527

496528
boolean actualNewSynchronization = newSynchronization &&
497529
!TransactionSynchronizationManager.isSynchronizationActive();
498-
if (actualNewSynchronization) {
499-
TransactionSynchronizationManager.setActualTransactionActive(transaction != null);
500-
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
501-
(definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ?
502-
definition.getIsolationLevel() : null);
503-
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
504-
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
505-
TransactionSynchronizationManager.initSynchronization();
530+
try {
531+
if (actualNewSynchronization) {
532+
TransactionSynchronizationManager.setActualTransactionActive(transaction != null);
533+
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
534+
(definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ?
535+
definition.getIsolationLevel() : null);
536+
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
537+
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
538+
TransactionSynchronizationManager.initSynchronization();
539+
}
540+
return new DefaultTransactionStatus(
541+
transaction, newTransaction, actualNewSynchronization,
542+
definition.isReadOnly(), debug, suspendedResources);
543+
}
544+
catch (Error err) {
545+
// Can only really be an OutOfMemoryError...
546+
if (actualNewSynchronization) {
547+
TransactionSynchronizationManager.clear();
548+
}
549+
throw err;
506550
}
507-
return new DefaultTransactionStatus(
508-
transaction, newTransaction, actualNewSynchronization,
509-
definition.isReadOnly(), debug, suspendedResources);
510551
}
511552

512553
/**
@@ -984,7 +1025,7 @@ private void cleanupAfterCompletion(DefaultTransactionStatus status) {
9841025
}
9851026
if (status.getSuspendedResources() != null) {
9861027
if (status.isDebug()) {
987-
logger.debug("Resuming suspended transaction");
1028+
logger.debug("Resuming suspended transaction after completion of inner transaction");
9881029
}
9891030
resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
9901031
}
@@ -1076,7 +1117,7 @@ protected boolean useSavepointForNestedTransaction() {
10761117
* @throws TransactionException in case of creation or system errors
10771118
*/
10781119
protected abstract void doBegin(Object transaction, TransactionDefinition definition)
1079-
throws TransactionException;
1120+
throws TransactionException;
10801121

10811122
/**
10821123
* Suspend the resources of the current transaction.

org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
* <p>Supports delegating savepoint-related methods to a transaction object
3232
* that implements the {@link SavepointManager} interface.
3333
*
34-
* <p><b>NOTE:</b> This is <i>not</i> intended to be used for other
35-
* PlatformTransactionManager implementations, in particular not for
36-
* mock transaction managers. Use {@link SimpleTransactionStatus} or
37-
* a mock for the plain TransactionStatus interface instead.
34+
* <p><b>NOTE:</b> This is <i>not</i> intended for use with other PlatformTransactionManager
35+
* implementations, in particular not for mock transaction managers in testing environments.
36+
* Use the alternative {@link SimpleTransactionStatus} class or a mock for the plain
37+
* {@link org.springframework.transaction.TransactionStatus} interface instead.
3838
*
3939
* @author Juergen Hoeller
4040
* @since 19.01.2004
@@ -77,7 +77,7 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
7777
* for this transaction, if any
7878
*/
7979
public DefaultTransactionStatus(
80-
Object transaction, boolean newTransaction, boolean newSynchronization,
80+
Object transaction, boolean newTransaction, boolean newSynchronization,
8181
boolean readOnly, boolean debug, Object suspendedResources) {
8282

8383
this.transaction = transaction;
@@ -88,6 +88,7 @@ public DefaultTransactionStatus(
8888
this.suspendedResources = suspendedResources;
8989
}
9090

91+
9192
/**
9293
* Return the underlying transaction object.
9394
*/

0 commit comments

Comments
 (0)