diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/ResourcelessTransactionManager.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/ResourcelessTransactionManager.java index f313c857c..ffce507ae 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/ResourcelessTransactionManager.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/ResourcelessTransactionManager.java @@ -16,24 +16,85 @@ package org.springframework.batch.support.transaction; +import java.util.Stack; + import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionSynchronizationManager; public class ResourcelessTransactionManager extends AbstractPlatformTransactionManager { protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { + ((ResourcelessTransaction) transaction).begin(); } protected void doCommit(DefaultTransactionStatus status) throws TransactionException { + logger.debug("Committing resourceless transaction on [" + status.getTransaction() + "]"); } protected Object doGetTransaction() throws TransactionException { - return new Object(); + Object transaction = new ResourcelessTransaction(); + Stack resources; + if (!TransactionSynchronizationManager.hasResource(this)) { + resources = new Stack(); + TransactionSynchronizationManager.bindResource(this, resources); + } + else { + @SuppressWarnings("unchecked") + Stack stack = (Stack) TransactionSynchronizationManager.getResource(this); + resources = stack; + } + resources.push(transaction); + return transaction; } protected void doRollback(DefaultTransactionStatus status) throws TransactionException { + logger.debug("Rolling back resourceless transaction on [" + status.getTransaction() + "]"); + } + + protected boolean isExistingTransaction(Object transaction) throws TransactionException { + if (TransactionSynchronizationManager.hasResource(this)) { + @SuppressWarnings("unchecked") + Stack stack = (Stack) TransactionSynchronizationManager.getResource(this); + return stack.size()>1; + } + return ((ResourcelessTransaction) transaction).isActive(); + } + + protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException { + } + + protected void doCleanupAfterCompletion(Object transaction) { + @SuppressWarnings("unchecked") + Stack list = (Stack) TransactionSynchronizationManager.getResource(this); + Stack resources = list; + if (!resources.isEmpty()) { + resources.pop(); + } + if (resources.isEmpty()) { + TransactionSynchronizationManager.unbindResource(this); + } + ((ResourcelessTransaction) transaction).clear(); + } + + private static class ResourcelessTransaction { + + private boolean active = false; + + public boolean isActive() { + return active; + } + + public void begin() { + active = true; + } + + public void clear() { + active = false; + } + } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ResourcelessTransactionManagerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ResourcelessTransactionManagerTests.java index 3fdcc6c4a..b107cc7b2 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ResourcelessTransactionManagerTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ResourcelessTransactionManagerTests.java @@ -27,9 +27,11 @@ import org.springframework.transaction.support.TransactionTemplate; public class ResourcelessTransactionManagerTests extends TestCase { - ResourcelessTransactionManager transactionManager = new ResourcelessTransactionManager(); + private ResourcelessTransactionManager transactionManager = new ResourcelessTransactionManager(); - int txStatus = Integer.MIN_VALUE; + private int txStatus = Integer.MIN_VALUE; + + private int count = 0; public void testCommit() throws Exception { new TransactionTemplate(transactionManager).execute(new TransactionCallback() { @@ -46,6 +48,32 @@ public class ResourcelessTransactionManagerTests extends TestCase { assertEquals(TransactionSynchronization.STATUS_COMMITTED, txStatus); } + public void testCommitNested() throws Exception { + final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + transactionTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { + public void afterCompletion(int status) { + super.afterCompletion(status); + txStatus = status; + count++; + } + }); + transactionTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + assertEquals(0, count); + count++; + return null; + } + }); + assertEquals(1, count); + return null; + } + }); + assertEquals(TransactionSynchronization.STATUS_COMMITTED, txStatus); + assertEquals(2, count); + } + public void testRollback() throws Exception { try { new TransactionTemplate(transactionManager).execute(new TransactionCallback() { @@ -66,4 +94,69 @@ public class ResourcelessTransactionManagerTests extends TestCase { } assertEquals(TransactionSynchronization.STATUS_ROLLED_BACK, txStatus); } + + public void testRollbackNestedInner() throws Exception { + final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + try { + transactionTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { + public void afterCompletion(int status) { + super.afterCompletion(status); + txStatus = status; + count++; + } + }); + transactionTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + assertEquals(0, count); + count++; + throw new RuntimeException("Rollback!"); + } + }); + assertEquals(1, count); + return null; + } + }); + fail("Expected RuntimeException"); + } + catch (RuntimeException e) { + assertEquals("Rollback!", e.getMessage()); + } + assertEquals(TransactionSynchronization.STATUS_ROLLED_BACK, txStatus); + assertEquals(2, count); + } + + public void testRollbackNestedOuter() throws Exception { + final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + try { + transactionTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { + public void afterCompletion(int status) { + super.afterCompletion(status); + txStatus = status; + count++; + } + }); + transactionTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + assertEquals(0, count); + count++; + return null; + } + }); + assertEquals(1, count); + throw new RuntimeException("Rollback!"); + } + }); + fail("Expected RuntimeException"); + } + catch (RuntimeException e) { + assertEquals("Rollback!", e.getMessage()); + } + assertEquals(TransactionSynchronization.STATUS_ROLLED_BACK, txStatus); + assertEquals(2, count); + } + }