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

rzo1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git


The following commit(s) were added to refs/heads/main by this push:
     new b6eeafbc52 TOMEE-4727 - Run Jakarta Data repository calls in a new 
transaction when the bound one is no longer active (#3063)
b6eeafbc52 is described below

commit b6eeafbc52a786e06a2555db155fa31d127641b8
Author: Markus Jung <[email protected]>
AuthorDate: Wed Oct 7 17:36:23 2026 +0200

    TOMEE-4727 - Run Jakarta Data repository calls in a new transaction when 
the bound one is no longer active (#3063)
---
 .../arquillian/tests/data/ItemInserted.java        | 23 ++++++++++++
 ...lItemService.java => ItemInsertedObserver.java} | 41 ++++++++++------------
 .../tests/data/JakartaDataTransactionalTest.java   | 18 ++++++++++
 .../tests/data/TransactionalItemService.java       | 11 ++++++
 .../data/handler/RepositoryInvocationHandler.java  | 12 ++++++-
 5 files changed, 81 insertions(+), 24 deletions(-)

diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/ItemInserted.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/ItemInserted.java
new file mode 100644
index 0000000000..54a5dd27ab
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/ItemInserted.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.arquillian.tests.data;
+
+/**
+ * Fired by {@link TransactionalItemService#insertAndNotify(String)} once the 
item is inserted.
+ */
+public record ItemInserted(Long id) {
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/ItemInsertedObserver.java
similarity index 51%
copy from 
arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
copy to 
arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/ItemInsertedObserver.java
index b63ba9e7a7..349f951ae5 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/ItemInsertedObserver.java
@@ -17,42 +17,37 @@
 package org.apache.openejb.arquillian.tests.data;
 
 import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.event.TransactionPhase;
 import jakarta.inject.Inject;
-import jakarta.transaction.Transactional;
 
 /**
- * Calls {@link SimpleItemRepository} from inside CDI {@code @Transactional} 
methods, where the
- * JTA spec forbids any use of {@code UserTransaction}.
+ * Reads the inserted item back through {@link SimpleItemRepository} after the 
inserting
+ * transaction commits. The container catches and logs any exception a 
transactional observer
+ * throws, so the outcome is recorded for the test to assert on.
  */
 @ApplicationScoped
-public class TransactionalItemService {
+public class ItemInsertedObserver {
 
     @Inject
     private SimpleItemRepository repository;
 
-    @Transactional
-    public Long insert(final String label) {
-        return repository.insert(new SimpleItem(label)).getId();
-    }
+    private String label;
+    private Exception failure;
 
-    @Transactional
-    public String findLabel(final Long id) {
-        return repository.findById(id).map(SimpleItem::getLabel).orElse(null);
+    public void onItemInserted(@Observes(during = 
TransactionPhase.AFTER_SUCCESS) final ItemInserted event) {
+        try {
+            label = 
repository.findById(event.id()).map(SimpleItem::getLabel).orElse(null);
+        } catch (final Exception e) {
+            failure = e;
+        }
     }
 
-    /**
-     * Loads the entity and saves it again within one transaction, so the 
instance handed to
-     * {@code save} is the managed one {@code findById} returned.
-     */
-    @Transactional
-    public void rename(final Long id, final String label) {
-        final SimpleItem item = repository.findById(id).orElseThrow();
-        item.setLabel(label);
-        repository.save(item);
+    public String getLabel() {
+        return label;
     }
 
-    @Transactional
-    public void delete(final Long id) {
-        repository.deleteById(id);
+    public Exception getFailure() {
+        return failure;
     }
 }
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/JakartaDataTransactionalTest.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/JakartaDataTransactionalTest.java
index 01d10d7620..7550bd6060 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/JakartaDataTransactionalTest.java
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/JakartaDataTransactionalTest.java
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -69,6 +70,8 @@ public class JakartaDataTransactionalTest {
             .addClass(SimpleItem.class)
             .addClass(SimpleItemRepository.class)
             .addClass(TransactionalItemService.class)
+            .addClass(ItemInserted.class)
+            .addClass(ItemInsertedObserver.class)
             .addAsResource(new StringAsset(PERSISTENCE_XML), 
"META-INF/persistence.xml")
             .addAsWebInfResource(new StringAsset(BEANS_XML), "beans.xml");
     }
@@ -79,6 +82,9 @@ public class JakartaDataTransactionalTest {
     @Inject
     private SimpleItemRepository repository;
 
+    @Inject
+    private ItemInsertedObserver observer;
+
     @Test
     public void insertAndFindInsideTransactional() {
         final Long id = service.insert("tx-insert");
@@ -104,4 +110,16 @@ public class JakartaDataTransactionalTest {
 
         assertTrue("Deleted item should not be found", 
repository.findById(id).isEmpty());
     }
+
+    /**
+     * An after success observer runs in the transaction's after completion 
phase, where the
+     * committed transaction is still associated with the thread but no longer 
active.
+     */
+    @Test
+    public void findInsideAfterSuccessObserver() {
+        service.insertAndNotify("tx-observed");
+
+        assertNull("Repository call in the observer failed: " + 
observer.getFailure(), observer.getFailure());
+        assertEquals("tx-observed", observer.getLabel());
+    }
 }
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
index b63ba9e7a7..765802500b 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/java/org/apache/openejb/arquillian/tests/data/TransactionalItemService.java
@@ -17,6 +17,7 @@
 package org.apache.openejb.arquillian.tests.data;
 
 import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Event;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
 
@@ -30,11 +31,21 @@ public class TransactionalItemService {
     @Inject
     private SimpleItemRepository repository;
 
+    @Inject
+    private Event<ItemInserted> itemInserted;
+
     @Transactional
     public Long insert(final String label) {
         return repository.insert(new SimpleItem(label)).getId();
     }
 
+    @Transactional
+    public Long insertAndNotify(final String label) {
+        final Long id = repository.insert(new SimpleItem(label)).getId();
+        itemInserted.fire(new ItemInserted(id));
+        return id;
+    }
+
     @Transactional
     public String findLabel(final Long id) {
         return repository.findById(id).map(SimpleItem::getLabel).orElse(null);
diff --git 
a/container/openejb-jakarta-data/src/main/java/org/apache/openejb/data/handler/RepositoryInvocationHandler.java
 
b/container/openejb-jakarta-data/src/main/java/org/apache/openejb/data/handler/RepositoryInvocationHandler.java
index 46869b2625..581e8a87ee 100644
--- 
a/container/openejb-jakarta-data/src/main/java/org/apache/openejb/data/handler/RepositoryInvocationHandler.java
+++ 
b/container/openejb-jakarta-data/src/main/java/org/apache/openejb/data/handler/RepositoryInvocationHandler.java
@@ -30,6 +30,7 @@ import jakarta.data.repository.Update;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.TypedQuery;
 import jakarta.transaction.Status;
+import jakarta.transaction.Transaction;
 import jakarta.transaction.TransactionManager;
 import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolationException;
@@ -172,7 +173,12 @@ public class RepositoryInvocationHandler implements 
InvocationHandler {
             return doInvoke(em, method, args);
         }
 
-        final boolean startedTx = tm.getStatus() == 
Status.STATUS_NO_TRANSACTION;
+        final int status = tm.getStatus();
+        final boolean startedTx = status != Status.STATUS_ACTIVE && status != 
Status.STATUS_MARKED_ROLLBACK;
+
+        // A transaction that is completing or completed, as seen from an 
after completion callback such as a
+        // CDI after success observer, is still bound to the thread but can no 
longer be joined.
+        final Transaction completedTx = startedTx && status != 
Status.STATUS_NO_TRANSACTION ? tm.suspend() : null;
         if (startedTx) {
             tm.begin();
         }
@@ -191,6 +197,10 @@ public class RepositoryInvocationHandler implements 
InvocationHandler {
                 }
             }
             throw mapException(e);
+        } finally {
+            if (completedTx != null) {
+                tm.resume(completedTx);
+            }
         }
     }
 

Reply via email to