You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/07/23 20:04:50 UTC

[isis] branch master updated: ISIS-2573: remove TransactionService#nextTransaction()

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 7e730cb  ISIS-2573: remove TransactionService#nextTransaction()
7e730cb is described below

commit 7e730cbf8ddc2fd29f687facf50f11a099899325
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Jul 23 22:04:33 2021 +0200

    ISIS-2573: remove TransactionService#nextTransaction()
    
    also fixes most publishing tests
---
 .../applib/services/xactn/TransactionService.java  |  13 +-
 .../transaction/TransactionServiceSpring.java      |  96 +++++++-------
 .../publishing/jpa/JpaPropertyPublishingTest.java  |   1 +
 .../publishing/PublishingTestFactoryAbstract.java  | 105 +++++++++------
 .../publishing/PublishingTestFactoryJdo.java       | 145 +++++++--------------
 .../publishing/PublishingTestFactoryJpa.java       | 142 +++++++-------------
 6 files changed, 223 insertions(+), 279 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java b/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java
index f232e25..8fa2cc5 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java
@@ -62,9 +62,14 @@ public interface TransactionService extends TransactionalProcessor {
      */
     void flushTransaction();
 
-    /**
-     * Commits the current thread's transaction (if there is one), and in any case begins a new one.
-     */
-    void nextTransaction();
+
+    //XXX we removed the entire method, because of following subtlety with JpaTransactionManager's commit
+    // If the transaction wasn't a new one, omit the commit for proper participation in
+    // the surrounding transaction. If a previous transaction has been suspended to be
+    // able to create a new one, resume the previous transaction after committing the new one.
+//    /**
+//     * Commits the current thread's transaction (if there is one), and in any case begins a new one.
+//     */
+//    void nextTransaction();
 
 }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
index 9825ae7..904c984 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -70,7 +70,7 @@ import lombok.extern.log4j.Log4j2;
 public class TransactionServiceSpring
 implements
     TransactionService,
-        TransactionBoundaryAware {
+    TransactionBoundaryAware {
 
     private final Can<PlatformTransactionManager> platformTransactionManagers;
     private final InteractionLayerTracker interactionLayerTracker;
@@ -95,7 +95,7 @@ implements
     // -- SPRING INTEGRATION
 
     @Override
-    public <T> Result<T> callTransactional(TransactionDefinition def, Callable<T> callable) {
+    public <T> Result<T> callTransactional(final TransactionDefinition def, final Callable<T> callable) {
 
         val txManager = transactionManagerForElseFail(def);
 
@@ -128,45 +128,49 @@ implements
         return result;
     }
 
-    @Override
-    public void nextTransaction() {
-
-        val txManager = singletonTransactionManagerElseFail();
-
-        try {
-
-            val txTemplate = new TransactionTemplate(txManager);
-            txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
-
-            // either reuse existing or create new
-            val txStatus = txManager.getTransaction(txTemplate);
-            if(txStatus.isNewTransaction()) {
-                // we have created a new transaction, so we are done
-                return;
-            }
-            // we are reusing an exiting transaction, so end it and create a new one afterwards
-            if(txStatus.isRollbackOnly()) {
-                txManager.rollback(txStatus);
-            } else {
-                txManager.commit(txStatus);
-            }
-
-            // begin a new transaction
-            txManager.getTransaction(txTemplate);
-
-        } catch (RuntimeException ex) {
-
-            val translatedEx = translateExceptionIfPossible(ex, txManager);
-
-            if(translatedEx instanceof RuntimeException) {
-                throw ex;
-            }
-
-            throw new RuntimeException(ex);
-
-        }
-
-    }
+//    @Override
+//    public void nextTransaction() {
+//
+//        val txManager = singletonTransactionManagerElseFail();
+//
+//        try {
+//
+//            val txTemplate = new TransactionTemplate(txManager);
+//            txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+//
+//            // either reuse existing or create new
+//            val txStatus = txManager.getTransaction(txTemplate);
+//            if(txStatus.isNewTransaction()) {
+//                // we have created a new transaction, so we are done
+//                return;
+//            }
+//            // we are reusing an exiting transaction, so end it and create a new one afterwards
+//            if(txStatus.isRollbackOnly()) {
+//                txManager.rollback(txStatus);
+//            } else {
+//                //XXX we removed the entire method, because of following subtlety
+                  // If the transaction wasn't a new one, omit the commit for proper participation in
+//                // the surrounding transaction. If a previous transaction has been suspended to be
+//                // able to create a new one, resume the previous transaction after committing the new one.
+//                txManager.commit(txStatus);
+//           }
+//
+//            // begin a new transaction
+//            txManager.getTransaction(txTemplate);
+//
+//        } catch (RuntimeException ex) {
+//
+//            val translatedEx = translateExceptionIfPossible(ex, txManager);
+//
+//            if(translatedEx instanceof RuntimeException) {
+//                throw ex;
+//            }
+//
+//            throw new RuntimeException(ex);
+//
+//        }
+//
+//    }
 
     @Override
     public void flushTransaction() {
@@ -230,25 +234,25 @@ implements
 
     /** INTERACTION BEGIN BOUNDARY */
     @Override
-    public void beforeEnteringTransactionalBoundary(Interaction interaction) {
+    public void beforeEnteringTransactionalBoundary(final Interaction interaction) {
         txCounter.get().reset();
     }
 
     /** TRANSACTION END BOUNDARY */
     @EventListener(TransactionAfterCompletionEvent.class)
-    public void onTransactionEnded(TransactionAfterCompletionEvent event) {
+    public void onTransactionEnded(final TransactionAfterCompletionEvent event) {
         txCounter.get().increment();
     }
 
     /** INTERACTION END BOUNDARY */
     @Override
-    public void afterLeavingTransactionalBoundary(Interaction interaction) {
+    public void afterLeavingTransactionalBoundary(final Interaction interaction) {
         txCounter.remove(); //XXX not tested yet: can we be certain that no txCounter.get() is called afterwards?
     }
 
     // -- HELPER
 
-    private PlatformTransactionManager transactionManagerForElseFail(TransactionDefinition def) {
+    private PlatformTransactionManager transactionManagerForElseFail(final TransactionDefinition def) {
         if(def instanceof TransactionTemplate) {
             val txManager = ((TransactionTemplate)def).getTransactionManager();
             if(txManager!=null) {
@@ -291,7 +295,7 @@ implements
 
     }
 
-    private Throwable translateExceptionIfPossible(Throwable ex, PlatformTransactionManager txManager) {
+    private Throwable translateExceptionIfPossible(final Throwable ex, final PlatformTransactionManager txManager) {
 
         if(ex instanceof DataAccessException) {
             return ex; // nothing to do, already translated
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaPropertyPublishingTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaPropertyPublishingTest.java
index dc33e86..40d26f1 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaPropertyPublishingTest.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaPropertyPublishingTest.java
@@ -50,6 +50,7 @@ import lombok.val;
                 //XrayEnable.class
         },
         properties = {
+                "logging.level.org.springframework.orm.jpa.*=DEBUG",
                 "logging.level.org.apache.isis.applib.services.publishing.log.*=DEBUG",
                 "logging.level.org.apache.isis.testdomain.util.rest.KVStoreForTesting=DEBUG",
                 "logging.level.org.apache.isis.core.transaction.changetracking.EntityChangeTrackerDefault=DEBUG",
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
index 5bbc386..51b28d8 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
@@ -39,10 +39,11 @@ import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.applib.services.xactn.TransactionState;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.assertions._Assert;
+import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.commons.internal.debug.xray.XrayUi;
 import org.apache.isis.core.security.util.XrayUtil;
+import org.apache.isis.core.transaction.events.TransactionAfterCompletionEvent;
 import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
-import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.PublishingTestContext.TraceLog;
 
 import lombok.Getter;
 import lombok.NonNull;
@@ -63,6 +64,13 @@ public abstract class PublishingTestFactoryAbstract {
     @Value(staticConstructor = "of")
     public static class PublishingTestContext {
 
+        public static PublishingTestContext of(
+                final Runnable given,
+                final Consumer<VerificationStage> verifier,
+                final VerificationStage completionStage) {
+            return PublishingTestContext.of(new TraceLog(), given, verifier, completionStage);
+        }
+
         public static class TraceLog {
             private final StringBuilder buffer = new StringBuilder();
             @Getter private boolean debug = true;
@@ -80,6 +88,7 @@ public abstract class PublishingTestFactoryAbstract {
         private final @NonNull TraceLog traceLog;
         private final @NonNull Runnable given;
         private final @NonNull Consumer<VerificationStage> verifier;
+        private final @NonNull VerificationStage transactionCompletionStage;
 
         public void runGiven() {
             traceLog.log("2.1 about to run given");
@@ -94,17 +103,9 @@ public abstract class PublishingTestFactoryAbstract {
         /**
          * @param preCommitListener - shared instance
          */
-        public void bind(final PreCommitListener preCommitListener) {
-            traceLog.log("1. bind to pre-commit events");
-            preCommitListener.bind(verifier);
-        }
-
-        /**
-         * @param preCommitListener - shared instance
-         */
-        public void unbind(final PreCommitListener preCommitListener) {
-            traceLog.log("3. unbind from pre-commit events");
-            preCommitListener.unbind(verifier);
+        public void bind(final CommitListener preCommitListener) {
+            traceLog.log("1. bind to commit events");
+            preCommitListener.bind(this);
         }
 
         public void changeProperty(final Runnable runnable) {
@@ -113,6 +114,11 @@ public abstract class PublishingTestFactoryAbstract {
             traceLog.log("2.4 book's name has changed");
         }
 
+        public void runPostCommitVerify() {
+            traceLog.log("4. post commit verify %s", transactionCompletionStage);
+            runVerify(transactionCompletionStage);
+        }
+
     }
 
     @FunctionalInterface
@@ -121,31 +127,43 @@ public abstract class PublishingTestFactoryAbstract {
     }
 
     @Service
-    public static class PreCommitListener {
+    public static class CommitListener {
 
-        private Consumer<VerificationStage> verifier;
-//        @Setter private TraceLog traceLog;
+        private PublishingTestContext testContext;
 
-        /** TRANSACTION END BOUNDARY */
+        /** TRANSACTION END BOUNDARY (PRE) */
         @EventListener(TransactionBeforeCompletionEvent.class)
         public void onPreCommit(final TransactionBeforeCompletionEvent event) {
-//            if(traceLog!=null) {
-//                traceLog.log("=== TRANSACTION END BOUNDARY");
-//            }
-            if(verifier!=null) {
-                verifier.accept(VerificationStage.PRE_COMMIT);
+            _Probe.errOut("=== TRANSACTION before completion");
+            if(testContext!=null) {
+                testContext.getTraceLog().log("3. pre-commit event is occurring");
+                testContext.runVerify(VerificationStage.PRE_COMMIT);
             }
         }
 
-        public void bind(final Consumer<VerificationStage> verifier) {
-            _Assert.assertNull(this.verifier, "PreCommitListener is already bound to a verifier.");
-            this.verifier = verifier;
+        /** TRANSACTION END BOUNDARY (POST)*/
+        @EventListener(TransactionAfterCompletionEvent.class)
+        public void onPreCommit(final TransactionAfterCompletionEvent event) {
+            _Probe.errOut("=== TRANSACTION after completion");
+            if(testContext!=null) {
+                try {
+                    testContext.runPostCommitVerify();
+                } finally {
+                    unbind(testContext);
+                }
+            }
         }
 
-        public void unbind(final Consumer<VerificationStage> verifier) {
-            _Assert.assertEquals(this.verifier, verifier, "PreCommitListener is not bound to the verifier, "
+        public void bind(final PublishingTestContext testContext) {
+            _Assert.assertNull(this.testContext, "PreCommitListener is already bound to a testContext.");
+            this.testContext = testContext;
+        }
+
+        public void unbind(final PublishingTestContext testContext) {
+            _Assert.assertEquals(this.testContext, testContext, "PreCommitListener is not bound to the testContext, "
                     + "which it receives a request to unbind from.");
-            this.verifier = null;
+            this.testContext.getTraceLog().log("5. unbind from commit events");
+            this.testContext = null;
         }
 
     }
@@ -161,30 +179,32 @@ public abstract class PublishingTestFactoryAbstract {
             final Runnable given,
             final Consumer<VerificationStage> verifier) {
 
+        _Probe.errOut("GENERATE TESTS");
+
         val dynamicTests = Can.<DynamicTest>of(
 
                 publishingTest("Programmatic Execution",
-                        given, verifier,
+                        PublishingTestContext.of(given, verifier, VerificationStage.POST_COMMIT_WHEN_PROGRAMMATIC),
                         VerificationStage.POST_INTERACTION_WHEN_PROGRAMMATIC,
                         this::programmaticExecution),
                 publishingTest("Interaction Api Execution",
-                        given, verifier,
+                        PublishingTestContext.of(given, verifier, VerificationStage.POST_COMMIT),
                         VerificationStage.POST_INTERACTION,
                         this::interactionApiExecution),
                 publishingTest("Wrapper Sync Execution w/o Rules",
-                        given, verifier,
+                        PublishingTestContext.of(given, verifier, VerificationStage.POST_COMMIT),
                         VerificationStage.POST_INTERACTION,
                         this::wrapperSyncExecutionNoRules),
                 publishingTest("Wrapper Sync Execution w/ Rules (expected to fail w/ DisabledException)",
-                        given, verifier,
+                        PublishingTestContext.of(given, verifier, VerificationStage.FAILURE_CASE),
                         VerificationStage.POST_INTERACTION,
                         this::wrapperSyncExecutionWithFailure),
                 publishingTest("Wrapper Async Execution w/o Rules",
-                        given, verifier,
+                        PublishingTestContext.of(given, verifier, VerificationStage.POST_COMMIT),
                         VerificationStage.POST_INTERACTION,
                         this::wrapperAsyncExecutionNoRules),
                 publishingTest("Wrapper Async Execution w/ Rules (expected to fail w/ DisabledException)",
-                        given, verifier,
+                        PublishingTestContext.of(given, verifier, VerificationStage.FAILURE_CASE),
                         VerificationStage.POST_INTERACTION,
                         this::wrapperAsyncExecutionWithFailure)
                 );
@@ -198,6 +218,8 @@ public abstract class PublishingTestFactoryAbstract {
 
     }
 
+    protected abstract void setupEntity(PublishingTestContext context);
+
     protected abstract boolean programmaticExecution(PublishingTestContext context);
 
     protected abstract boolean interactionApiExecution(PublishingTestContext context);
@@ -215,8 +237,7 @@ public abstract class PublishingTestFactoryAbstract {
 
     private final DynamicTest publishingTest(
             final String displayName,
-            final Runnable given,
-            final Consumer<VerificationStage> verifier,
+            final PublishingTestContext testContext,
             final VerificationStage onSuccess,
             final PublishingTestRunner testRunner) {
 
@@ -227,12 +248,20 @@ public abstract class PublishingTestFactoryAbstract {
             assertFalse(getInteractionService().isInInteraction());
             assert_no_initial_tx_context();
 
-            val traceLog = new TraceLog();
+            getInteractionService().runAnonymous(()->{
+                val currentInteraction = getInteractionService().currentInteraction();
+                xrayEnterInteraction(currentInteraction);
+                setupEntity(testContext);
+                xrayExitInteraction();
+            });
+
+            assertFalse(getInteractionService().isInInteraction());
+            assert_no_initial_tx_context();
 
             final boolean isSuccesfulRun = getInteractionService().callAnonymous(()->{
                 val currentInteraction = getInteractionService().currentInteraction();
                 xrayEnterInteraction(currentInteraction);
-                val result = testRunner.run(PublishingTestContext.of(traceLog, given, verifier));
+                val result = testRunner.run(testContext);
                 xrayExitInteraction();
                 return result;
             });
@@ -240,7 +269,7 @@ public abstract class PublishingTestFactoryAbstract {
             getInteractionService().closeInteractionLayers();
 
             if(isSuccesfulRun) {
-                verifier.accept(onSuccess);
+                testContext.runVerify(onSuccess);
             }
 
         });
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java
index 77dab90..df2f20d 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java
@@ -30,7 +30,6 @@ import javax.jdo.PersistenceManagerFactory;
 
 import org.springframework.context.annotation.Import;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Propagation;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -52,7 +51,7 @@ import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
 import org.apache.isis.testdomain.jdo.entities.JdoBook;
 import org.apache.isis.testdomain.jdo.entities.JdoInventory;
 import org.apache.isis.testdomain.jdo.entities.JdoProduct;
-import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.PreCommitListener;
+import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.CommitListener;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
 
 import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
@@ -60,11 +59,12 @@ import static org.apache.isis.applib.services.wrapper.control.AsyncControl.retur
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
 import lombok.val;
 
 @Component
 @Import({
-    PreCommitListener.class
+    CommitListener.class
 })
 @RequiredArgsConstructor(onConstructor_ = {@Inject})
 public class PublishingTestFactoryJdo
@@ -74,7 +74,7 @@ extends PublishingTestFactoryAbstract {
     private final WrapperFactory wrapper;
     private final ObjectManager objectManager;
     private final FixtureScripts fixtureScripts;
-    private final PreCommitListener preCommitListener;
+    private final CommitListener preCommitListener;
 
     @Getter(onMethod_ = {@Override}, value = AccessLevel.PROTECTED)
     private final InteractionService interactionService;
@@ -85,18 +85,27 @@ extends PublishingTestFactoryAbstract {
     @Named("transaction-aware-pmf-proxy")
     private final PersistenceManagerFactory pmf;
 
+    // -- TEST SETUP
+
+    @Override
+    protected void setupEntity(final PublishingTestContext context) {
+        // given
+        setupBookForJdo();
+    }
+
     // -- TESTS - PROGRAMMATIC
 
     @Override
     protected boolean programmaticExecution(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJdo();
-
         context.bind(preCommitListener);
 
-        withBookDoTransactional(book->{
+        // This test does not trigger command or execution publishing, however it does trigger
+        // entity-change-publishing.
+
+
+        withBookDo(book->{
 
             context.runGiven();
 
@@ -107,14 +116,6 @@ extends PublishingTestFactoryAbstract {
 
         });
 
-        context.unbind(preCommitListener);
-
-        // This test does not trigger command or execution publishing, however it does trigger
-        // entity-change-publishing.
-
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT_WHEN_PROGRAMMATIC);
-
         return true;
     }
 
@@ -124,16 +125,13 @@ extends PublishingTestFactoryAbstract {
     protected boolean interactionApiExecution(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJdo();
+        context.bind(preCommitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when
             context.changeProperty(()->{
 
@@ -149,11 +147,6 @@ extends PublishingTestFactoryAbstract {
 
         });
 
-        context.unbind(preCommitListener);
-
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT);
-
         return true;
     }
 
@@ -163,27 +156,19 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperSyncExecutionNoRules(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJdo();
+        context.bind(preCommitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when - running synchronous
             val syncControl = SyncControl.control().withSkipRules(); // don't enforce rules
             context.changeProperty(()->wrapper.wrap(book, syncControl).setName("Book #2"));
 
-            context.unbind(preCommitListener);
-
         });
 
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT);
-
         return true;
     }
 
@@ -191,16 +176,13 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperSyncExecutionWithFailure(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJdo();
+        context.bind(preCommitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when - running synchronous
             val syncControl = SyncControl.control().withCheckRules(); // enforce rules
 
@@ -208,14 +190,8 @@ extends PublishingTestFactoryAbstract {
                 wrapper.wrap(book, syncControl).setName("Book #2"); // should fail with DisabledException
             });
 
-            context.unbind(preCommitListener);
-
         });
 
-
-        // then
-        context.runVerify(VerificationStage.FAILURE_CASE);
-
         return false;
     }
 
@@ -225,17 +201,18 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperAsyncExecutionNoRules(
             final PublishingTestContext context) throws InterruptedException, ExecutionException, TimeoutException {
 
+        context.bind(preCommitListener);
+
         // given
-        setupBookForJdo();
         val asyncControl = returningVoid().withSkipRules(); // don't enforce rules
 
         // when
 
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
+
 
             // when - running asynchronous
             wrapper.asyncWrap(book, asyncControl)
@@ -245,11 +222,6 @@ extends PublishingTestFactoryAbstract {
 
         asyncControl.getFuture().get(10, TimeUnit.SECONDS);
 
-        context.unbind(preCommitListener);
-
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT);
-
         return true;
     }
 
@@ -257,16 +229,13 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperAsyncExecutionWithFailure(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJdo();
+        context.bind(preCommitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when - running synchronous
             val asyncControl = returningVoid().withCheckRules(); // enforce rules
 
@@ -277,13 +246,8 @@ extends PublishingTestFactoryAbstract {
                 fail("unexpected code reach");
             });
 
-            context.unbind(preCommitListener);
-
         });
 
-        // then
-        context.runVerify(VerificationStage.FAILURE_CASE);
-
         return false;
     }
 
@@ -291,50 +255,39 @@ extends PublishingTestFactoryAbstract {
 
     private void setupBookForJdo() {
 
-        transactionService.runTransactional(Propagation.REQUIRES_NEW, ()->{
-            val pm = pmf.getPersistenceManager();
-
-            // cleanup
-            fixtureScripts.runPersona(JdoTestDomainPersona.PurgeAll);
+        val pm = pmf.getPersistenceManager();
 
-            // given Inventory with 1 Book
+        // cleanup
+        fixtureScripts.runPersona(JdoTestDomainPersona.PurgeAll);
 
-            val products = new HashSet<JdoProduct>();
+        // given Inventory with 1 Book
 
-            products.add(JdoBook.of(
-                    "Sample Book", "A sample book for testing.", 99.,
-                    "Sample Author", "Sample ISBN", "Sample Publisher"));
+        val products = new HashSet<JdoProduct>();
 
-            val inventory = JdoInventory.of("Sample Inventory", products);
-            pm.makePersistent(inventory);
+        products.add(JdoBook.of(
+                "Sample Book", "A sample book for testing.", 99.,
+                "Sample Author", "Sample ISBN", "Sample Publisher"));
 
-            inventory.getProducts().forEach(product->{
-                val prod = pm.makePersistent(product);
+        val inventory = JdoInventory.of("Sample Inventory", products);
+        pm.makePersistent(inventory);
 
-                _Probe.errOut("PROD ID: %s", JDOHelper.getObjectId(prod));
+        inventory.getProducts().forEach(product->{
+            val prod = pm.makePersistent(product);
 
-            });
-
-            //fixtureScripts.runPersona(JdoTestDomainPersona.InventoryWith1Book);
-
-            pm.flush();
+            _Probe.errOut("PROD ID: %s", JDOHelper.getObjectId(prod));
 
         });
-    }
 
-    private void withBookDoTransactional(final CheckedConsumer<JdoBook> transactionalBookConsumer) {
+        //fixtureScripts.runPersona(JdoTestDomainPersona.InventoryWith1Book);
 
-        xrayEnterTansaction(Propagation.REQUIRES_NEW);
+        pm.flush();
 
-        transactionService.runTransactional(Propagation.REQUIRES_NEW, ()->{
-            val book = repository.allInstances(JdoBook.class).listIterator().next();
-            transactionalBookConsumer.accept(book);
-
-        })
-        .optionalElseFail();
-
-        xrayExitTansaction();
     }
 
+    @SneakyThrows
+    private void withBookDo(final CheckedConsumer<JdoBook> transactionalBookConsumer) {
+        val book = repository.allInstances(JdoBook.class).listIterator().next();
+        transactionalBookConsumer.accept(book);
+    }
 
 }
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java
index a540521..e306d8b 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java
@@ -27,7 +27,6 @@ import javax.inject.Inject;
 
 import org.springframework.context.annotation.Import;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Propagation;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -50,7 +49,7 @@ import org.apache.isis.testdomain.jpa.JpaTestDomainPersona;
 import org.apache.isis.testdomain.jpa.entities.JpaBook;
 import org.apache.isis.testdomain.jpa.entities.JpaInventory;
 import org.apache.isis.testdomain.jpa.entities.JpaProduct;
-import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.PreCommitListener;
+import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.CommitListener;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
 
 import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
@@ -58,11 +57,12 @@ import static org.apache.isis.applib.services.wrapper.control.AsyncControl.retur
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
 import lombok.val;
 
 @Component
 @Import({
-    PreCommitListener.class
+    CommitListener.class
 })
 @RequiredArgsConstructor(onConstructor_ = {@Inject})
 public class PublishingTestFactoryJpa
@@ -72,7 +72,7 @@ extends PublishingTestFactoryAbstract {
     private final WrapperFactory wrapper;
     private final ObjectManager objectManager;
     private final FixtureScripts fixtureScripts;
-    private final PreCommitListener preCommitListener;
+    private final CommitListener commitListener;
     private final JpaSupportService jpaSupport;
 
     @Getter(onMethod_ = {@Override}, value = AccessLevel.PROTECTED)
@@ -81,6 +81,13 @@ extends PublishingTestFactoryAbstract {
     @Getter(onMethod_ = {@Override}, value = AccessLevel.PROTECTED)
     private final TransactionService transactionService;
 
+    // -- TEST SETUP
+
+    @Override
+    protected void setupEntity(final PublishingTestContext context) {
+        // given
+        setupBookForJpa();
+    }
 
     // -- TESTS - PROGRAMMATIC
 
@@ -88,12 +95,12 @@ extends PublishingTestFactoryAbstract {
     protected boolean programmaticExecution(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJpa();
+        context.bind(commitListener);
 
-        context.bind(preCommitListener);
+        // This test does not trigger command or execution publishing, however it does trigger
+        // entity-change-publishing.
 
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
@@ -104,13 +111,6 @@ extends PublishingTestFactoryAbstract {
 
         });
 
-        context.unbind(preCommitListener);
-
-        // This test does not trigger command or execution publishing, however it does trigger
-        // entity-change-publishing.
-
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT_WHEN_PROGRAMMATIC);
 
         return true;
     }
@@ -121,16 +121,13 @@ extends PublishingTestFactoryAbstract {
     protected boolean interactionApiExecution(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJpa();
+        context.bind(commitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when
             context.changeProperty(()->{
 
@@ -146,11 +143,6 @@ extends PublishingTestFactoryAbstract {
 
         });
 
-        context.unbind(preCommitListener);
-
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT);
-
         return true;
     }
 
@@ -160,27 +152,19 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperSyncExecutionNoRules(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJpa();
+        context.bind(commitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when - running synchronous
             val syncControl = SyncControl.control().withSkipRules(); // don't enforce rules
             context.changeProperty(()->wrapper.wrap(book, syncControl).setName("Book #2"));
 
-            context.unbind(preCommitListener);
-
         });
 
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT);
-
         return true;
     }
 
@@ -188,16 +172,13 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperSyncExecutionWithFailure(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJpa();
+        context.bind(commitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when - running synchronous
             val syncControl = SyncControl.control().withCheckRules(); // enforce rules
 
@@ -205,14 +186,8 @@ extends PublishingTestFactoryAbstract {
                 wrapper.wrap(book, syncControl).setName("Book #2"); // should fail with DisabledException
             });
 
-            context.unbind(preCommitListener);
-
         });
 
-
-        // then
-        context.runVerify(VerificationStage.FAILURE_CASE);
-
         return false;
     }
 
@@ -222,18 +197,17 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperAsyncExecutionNoRules(
             final PublishingTestContext context) throws InterruptedException, ExecutionException, TimeoutException {
 
+        context.bind(commitListener);
+
         // given
-        setupBookForJpa();
         val asyncControl = returningVoid().withSkipRules(); // don't enforce rules
 
         // when
 
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
             context.runGiven();
 
-            context.bind(preCommitListener);
-
             // when - running asynchronous
             wrapper.asyncWrap(book, asyncControl)
             .setName("Book #2");
@@ -242,11 +216,6 @@ extends PublishingTestFactoryAbstract {
 
         asyncControl.getFuture().get(10, TimeUnit.SECONDS);
 
-        context.unbind(preCommitListener);
-
-        // then
-        context.runVerify(VerificationStage.POST_COMMIT);
-
         return true;
     }
 
@@ -254,15 +223,12 @@ extends PublishingTestFactoryAbstract {
     protected boolean wrapperAsyncExecutionWithFailure(
             final PublishingTestContext context) {
 
-        // given
-        setupBookForJpa();
+        context.bind(commitListener);
 
         // when
-        withBookDoTransactional(book->{
+        withBookDo(book->{
 
-            context.runGiven();
 
-            context.bind(preCommitListener);
 
             // when - running synchronous
             val asyncControl = returningVoid().withCheckRules(); // enforce rules
@@ -274,13 +240,8 @@ extends PublishingTestFactoryAbstract {
                 fail("unexpected code reach");
             });
 
-            context.unbind(preCommitListener);
-
         });
 
-        // then
-        context.runVerify(VerificationStage.FAILURE_CASE);
-
         return false;
     }
 
@@ -288,50 +249,41 @@ extends PublishingTestFactoryAbstract {
 
     private void setupBookForJpa() {
 
-        transactionService.runTransactional(Propagation.REQUIRES_NEW, ()->{
-
-            val em = jpaSupport.getEntityManagerElseFail(JpaBook.class);
-
-            // cleanup
-            fixtureScripts.runPersona(JpaTestDomainPersona.PurgeAll);
+        val em = jpaSupport.getEntityManagerElseFail(JpaBook.class);
 
-            // given Inventory with 1 Book
+        // cleanup
+        fixtureScripts.runPersona(JpaTestDomainPersona.PurgeAll);
 
-            val products = new HashSet<JpaProduct>();
+        // given Inventory with 1 Book
 
-            products.add(JpaBook.of(
-                    "Sample Book", "A sample book for testing.", 99.,
-                    "Sample Author", "Sample ISBN", "Sample Publisher"));
+        val products = new HashSet<JpaProduct>();
 
-            val inventory = JpaInventory.of("Sample Inventory", products);
-            em.persist(inventory);
+        products.add(JpaBook.of(
+                "Sample Book", "A sample book for testing.", 99.,
+                "Sample Author", "Sample ISBN", "Sample Publisher"));
 
-            inventory.getProducts().forEach(product->{
-                em.persist(product);
+        val inventory = JpaInventory.of("Sample Inventory", products);
+        em.persist(inventory);
 
-                _Probe.errOut("PROD ID: %s", product.getId());
+        inventory.getProducts().forEach(product->{
+            em.persist(product);
 
-            });
-
-            //fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
-
-            em.flush();
+            _Probe.errOut("PROD ID: %s", product.getId());
 
         });
-    }
 
-    private void withBookDoTransactional(final CheckedConsumer<JpaBook> transactionalBookConsumer) {
+        //fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
 
-        xrayEnterTansaction(Propagation.REQUIRES_NEW);
+        em.flush();
+    }
 
-        transactionService.runTransactional(Propagation.REQUIRES_NEW, ()->{
-            val book = repository.allInstances(JpaBook.class).listIterator().next();
-            transactionalBookConsumer.accept(book);
+    @SneakyThrows
+    private void withBookDo(final CheckedConsumer<JpaBook> bookConsumer) {
 
-        })
-        .optionalElseFail();
+        val book = repository.allInstances(JpaBook.class).listIterator().next();
+        bookConsumer.accept(book);
 
-        xrayExitTansaction();
     }
 
+
 }