refactor: Proper wrap retryable exceptions in TransactionSystemException.

This avoids an attempted rollback when the commit already failed which will lead to an illegal state exception otherwise, which will completely throw off the transaction system.
Discovered through an internal card.
This commit is contained in:
Michael Simons
2024-10-10 16:00:26 +02:00
parent 673c3b51c1
commit eb6106b1e9
4 changed files with 23 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException;
import org.neo4j.driver.exceptions.SessionExpiredException;
import org.neo4j.driver.exceptions.TransientException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.transaction.TransactionSystemException;
/**
@@ -52,13 +53,17 @@ public final class RetryExceptionPredicate implements Predicate<Throwable> {
return true;
}
if (throwable instanceof TransactionSystemException && throwable.getCause() != null) {
throwable = throwable.getCause();
}
if (throwable instanceof IllegalStateException) {
String msg = throwable.getMessage();
return msg != null && RETRYABLE_ILLEGAL_STATE_MESSAGES.contains(msg);
}
Throwable ex = throwable;
if (throwable instanceof TransientDataAccessResourceException) {
if (throwable instanceof TransientDataAccessResourceException || throwable instanceof TransactionSystemException) {
ex = throwable.getCause();
}

View File

@@ -23,6 +23,8 @@ import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.TransactionConfig;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.exceptions.RetryableException;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -338,10 +340,17 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
@Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
Neo4jTransactionHolder transactionHolder = transactionObject.getRequiredResourceHolder();
Collection<Bookmark> newBookmarks = transactionHolder.commit();
this.bookmarkManager.resolve().updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
try {
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
Neo4jTransactionHolder transactionHolder = transactionObject.getRequiredResourceHolder();
Collection<Bookmark> newBookmarks = transactionHolder.commit();
this.bookmarkManager.resolve().updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
} catch (Neo4jException ex) {
if (ex instanceof RetryableException) {
throw new TransactionSystemException(ex.getMessage(), ex);
}
throw ex;
}
}
@Override

View File

@@ -21,6 +21,7 @@ import reactor.util.function.Tuples;
import org.apiguardian.api.API;
import org.neo4j.driver.Driver;
import org.neo4j.driver.TransactionConfig;
import org.neo4j.driver.exceptions.RetryableException;
import org.neo4j.driver.reactivestreams.ReactiveSession;
import org.neo4j.driver.reactivestreams.ReactiveTransaction;
import org.springframework.beans.BeansException;
@@ -35,6 +36,7 @@ import org.springframework.lang.Nullable;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.reactive.AbstractReactiveTransactionManager;
import org.springframework.transaction.reactive.GenericReactiveTransaction;
import org.springframework.transaction.reactive.TransactionSynchronizationManager;
@@ -326,6 +328,7 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
.getRequiredResourceHolder();
return holder.commit()
.doOnNext(bookmark -> bookmarkManager.resolve().updateBookmarks(holder.getBookmarks(), bookmark))
.onErrorMap(e -> e instanceof RetryableException, e -> new TransactionSystemException(e.getMessage(), e))
.then();
}

View File

@@ -323,7 +323,7 @@ class QuerydslNeo4jPredicateExecutorIT {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
List<Person> people = repository.findBy(predicate,
q -> q.limit(1)).all();
q -> q.sortBy(Sort.by("firstName").descending()).limit(1)).all();
assertThat(people).hasSize(1);
assertThat(people).extracting(Person::getFirstName).containsExactly("Helge");