From c088cfa33972232242f1c84b24ff8e75f3eda87f Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Wed, 13 Jun 2018 22:22:29 +1200 Subject: [PATCH] #1421 - Nested programatic transaction using auto close / try with resources ... does not end/rollback the parent/outer transaction --- .../java/io/ebeaninternal/api/ScopeTrans.java | 8 +- .../transaction/TestNestedTransaction.java | 238 ++++++++++++++++++ 2 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/tests/transaction/TestNestedTransaction.java diff --git a/src/main/java/io/ebeaninternal/api/ScopeTrans.java b/src/main/java/io/ebeaninternal/api/ScopeTrans.java index 27c0430c9..275f6c09a 100644 --- a/src/main/java/io/ebeaninternal/api/ScopeTrans.java +++ b/src/main/java/io/ebeaninternal/api/ScopeTrans.java @@ -53,6 +53,10 @@ public class ScopeTrans { */ private boolean rolledBack; + /** + * Flag set when nested commit has occurred. + */ + private boolean nestedCommit; public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) { @@ -113,7 +117,6 @@ public class ScopeTrans { complete(); } - /** * Complete the transaction programmatically. Try to commit. */ @@ -124,7 +127,7 @@ public class ScopeTrans { } public void end() { - if (created) { + if (created || !nestedCommit) { transaction.end(); } } @@ -133,6 +136,7 @@ public class ScopeTrans { if (created) { transaction.commit(); } else { + nestedCommit = true; transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery); if (restoreBatch != null) { transaction.setBatch(restoreBatch); diff --git a/src/test/java/org/tests/transaction/TestNestedTransaction.java b/src/test/java/org/tests/transaction/TestNestedTransaction.java new file mode 100644 index 000000000..57d61c82d --- /dev/null +++ b/src/test/java/org/tests/transaction/TestNestedTransaction.java @@ -0,0 +1,238 @@ +package org.tests.transaction; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.Transaction; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tests.model.basic.EBasic; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestNestedTransaction extends BaseTestCase { + + private static final Logger log = LoggerFactory.getLogger(TestNestedTransaction.class); + + private EBasic bean; + + @Before + public void init() { + bean = new EBasic("new"); + Ebean.save(bean); + } + + private void assertClean() { + EBasic myBean = Ebean.find(EBasic.class, bean.getId()); + assertThat(myBean.getName()).isEqualTo("new"); + } + + private void assertModified() { + EBasic myBean = Ebean.find(EBasic.class, bean.getId()); + assertThat(myBean.getName()).isEqualTo("modified"); + } + + private void modify() { + bean.setName("modified"); + Ebean.save(bean); + } + + // ===== level 0 ======= + @Test + public void testNested_0() { + try (Transaction txn0 = Ebean.beginTransaction()) { + modify(); + // no commit + } + assertClean(); + } + + @Test + public void testNested_1() { + try (Transaction txn0 = Ebean.beginTransaction()) { + modify(); + txn0.commit(); + } + assertModified(); + } + + // ===== level 1 ======= + @Test + public void testNested_00() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + modify(); + // no commit + } + // no commit + } + assertClean(); + } + + @Test + public void testNested_01() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + modify(); + // no commit + } + attemptCommit(txn0); + } + assertClean(); + } + + private void attemptCommit(Transaction txn) { + try { + txn.commit(); + } catch (IllegalStateException e) { + // expected + log.info("Expected IllegalStateException as transaction already rolled back " + e.getMessage()); + } + } + + @Test + public void testNested_10() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + modify(); + txn1.commit(); + } + // no commit + } + assertClean(); + } + + @Test + public void testNested_11() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + modify(); + txn1.commit(); + } + txn0.commit(); + } + assertModified(); + } + + // ===== level 2 ======= + @Test + public void testNested_000() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + // no commit + } + // no commit + } + // no commit + } + assertClean(); + } + + @Test + public void testNested_001() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + // no commit + } + // no commit + } + attemptCommit(txn0); + } + assertClean(); + } + + @Test + public void testNested_010() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + // no commit + } + txn1.commit(); + } + // no commit + } + assertClean(); + } + + @Test + public void testNested_011() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + // no commit + } + txn1.commit(); + } + attemptCommit(txn0); + } + assertClean(); + } + + @Test + public void testNested_100() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + txn2.commit(); + } + // no commit + } + // no commit + } + assertClean(); + } + + @Test + public void testNested_101() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + txn2.commit(); + } + // no commit + } + attemptCommit(txn0); + } + assertClean(); + } + + @Test + public void testNested_110() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + txn2.commit(); + } + txn1.commit(); + } + // no commit + } + assertClean(); + } + + @Test + public void testNested_111() { + try (Transaction txn0 = Ebean.beginTransaction()) { + try (Transaction txn1 = Ebean.beginTransaction()) { + try (Transaction txn2 = Ebean.beginTransaction()) { + modify(); + txn2.commit(); + } + txn1.commit(); + } + txn0.commit(); + } + assertModified(); + } +}