Merge pull request #3289 from FOCONIS/fix-nested-user-objects

Change semantic of userObjects in nested transaction
This commit is contained in:
Rob Bygrave
2024-02-15 22:10:45 +13:00
committed by GitHub
4 changed files with 83 additions and 6 deletions
@@ -3,6 +3,8 @@ package io.ebeaninternal.api;
import io.ebean.TxScope;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Used internally to handle the scoping of transactions for methods.
@@ -47,6 +49,11 @@ public final class ScopeTrans {
private boolean nestedCommit;
private boolean nestedUseSavepoint;
/**
* The UserObjects when using in nested transactions.
*/
private Map<String, Object> userObjects;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
this.rollbackOnChecked = rollbackOnChecked;
this.created = created;
@@ -218,4 +225,17 @@ public final class ScopeTrans {
return e instanceof RuntimeException || rollbackOnChecked;
}
public void putUserObject(String name, Object value) {
if (userObjects == null) {
userObjects = new HashMap<>();
}
userObjects.put(name, value);
}
public Object getUserObject(String name) {
if (userObjects == null) {
return null;
}
return userObjects.get(name);
}
}
@@ -2,7 +2,6 @@ package io.ebeaninternal.api;
import io.ebeaninternal.server.transaction.TransactionScopeManager;
import io.ebeaninternal.server.util.ArrayStack;
import jakarta.persistence.PersistenceException;
/**
@@ -173,4 +172,31 @@ public final class ScopedTransaction extends SpiTransactionProxy {
return current.caughtThrowable(e);
}
/**
* New user objects are always written to the current ScopeTrans.
*/
@Override
public void putUserObject(String name, Object value) {
current.putUserObject(name, value);
}
/**
* Returns the userObject in the stack, Herew we search
* the stack and return the first found userObject
*/
@Override
public Object getUserObject(String name) {
Object obj = current.getUserObject(name);
if (obj != null) {
return obj;
}
for (ScopeTrans trans : stack) {
obj = trans.getUserObject(name);
if (obj != null) {
return obj;
}
}
return transaction.getUserObject(name);
}
}
@@ -2,12 +2,13 @@ package io.ebeaninternal.server.util;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.Iterator;
import java.util.List;
/**
* Stack based on ArrayList.
*/
public class ArrayStack<E> {
public class ArrayStack<E> implements Iterable<E> {
private final List<E> list;
@@ -89,4 +90,9 @@ public class ArrayStack<E> {
public boolean contains(E o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
}
@@ -1,11 +1,14 @@
package org.tests.transaction;
import io.ebean.*;
import io.ebean.xtest.BaseTestCase;
import io.ebean.xtest.ForPlatform;
import io.ebean.DB;
import io.ebean.DataIntegrityException;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Transactional;
import io.ebean.xtest.BaseTestCase;
import io.ebean.xtest.ForPlatform;
import io.ebeaninternal.api.SpiTransaction;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
@@ -43,7 +46,7 @@ public class TestExecuteComplete extends BaseTestCase {
try {
DB.execute(TxScope.required().setBatch(PersistBatch.ALL), () ->
DB.execute(() -> {
DB.execute(() -> {
Customer customer = DB.reference(Customer.class, 42424242L);
Order order = new Order();
@@ -158,4 +161,26 @@ public class TestExecuteComplete extends BaseTestCase {
assertThat(getInScopeTransaction()).isNull();
}
@ForPlatform(Platform.H2)
@Test
public void test_nested_userobjects() {
try (Transaction txn1 = DB.beginTransaction()) {
assertThat(getInScopeTransaction()).isNotNull();
getInScopeTransaction().putUserObject("foo", "bar");
try (Transaction txn2 = DB.beginTransaction()) {
assertThat(getInScopeTransaction().getUserObject("foo")).isEqualTo("bar");
getInScopeTransaction().putUserObject("foo", "xxx");
getInScopeTransaction().putUserObject("test", "xxx");
assertThat(getInScopeTransaction().getUserObject("foo")).isEqualTo("xxx");
txn2.commit();
}
// CHECKME: What would we expect here? I would expect "bar" but get "xxx"
// NOTE: with TxScope.requiresNew() - I'll get "bar"
assertThat(getInScopeTransaction().getUserObject("test")).isNull();
assertThat(getInScopeTransaction().getUserObject("foo")).isEqualTo("bar");
}
}
}