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);
+ }
}
}