From d0aef64e33ae3f9189ac447bed730c2c714bd82b Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Mon, 8 Dec 2025 14:41:35 +0800 Subject: [PATCH] Retry on generating unique identifiers with MongoDB This commit will mitigate write conflict: ``` org.springframework.dao.DataIntegrityViolationException: Command failed with error 112 (WriteConflict): 'Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.' on server xxx.mongodb.net:1026. The full response is {"errorLabels": ["TransientTransactionError"], "ok": 0.0, "errmsg": "Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.", "code": 112, "codeName": "WriteConflict", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1755758536, "i": 4}}, "signature": {"hash": {"$binary": {"base64": "xxx=", "subType": "00"}}, "keyId": xxx}}, "operationTime": {"$timestamp": {"t": 1755758536, "i": 4}}} at org.springframework.data.mongodb.core.MongoExceptionTranslator.doTranslateException(MongoExceptionTranslator.java:141) ~[spring-data-mongodb-4.5.0.jar:4.5.0] at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:74) ~[spring-data-mongodb-4.5.0.jar:4.5.0] at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:3033) ~[spring-data-mongodb-4.5.0.jar:4.5.0] at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:609) ~[spring-data-mongodb-4.5.0.jar:4.5.0] at org.springframework.batch.core.repository.dao.MongoSequenceIncrementer.nextLongValue(MongoSequenceIncrementer.java:47) ~[spring-batch-core-5.2.2.jar:5.2.2] at org.springframework.batch.core.repository.dao.MongoJobInstanceDao.createJobInstance(MongoJobInstanceDao.java:80) ~[spring-batch-core-5.2.2.jar:5.2.2] ``` Closes GH-4960 Signed-off-by: Yanming Zhou --- .../dao/mongodb/MongoSequenceIncrementer.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java index 9722db637f..43ad9dfb54 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java @@ -19,7 +19,11 @@ import com.mongodb.client.model.ReturnDocument; import org.bson.Document; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; @@ -29,10 +33,14 @@ /** * @author Mahmoud Ben Hassine * @author Christoph Strobl + * @author Yanming Zhou * @since 5.2.0 */ public class MongoSequenceIncrementer implements DataFieldMaxValueIncrementer { + private final RetryTemplate retryTemplate = new RetryTemplate( + RetryPolicy.builder().includes(DataIntegrityViolationException.class).build()); + private final MongoOperations mongoTemplate; private final String sequenceName; @@ -44,11 +52,22 @@ public MongoSequenceIncrementer(MongoOperations mongoTemplate, String sequenceNa @Override public long nextLongValue() throws DataAccessException { - return mongoTemplate.execute("BATCH_SEQUENCES", - collection -> collection + try { + return retryTemplate + .execute(() -> mongoTemplate.execute("BATCH_SEQUENCES", collection -> collection .findOneAndUpdate(new Document("_id", sequenceName), new Document("$inc", new Document("count", 1)), new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)) - .getLong("count")); + .getLong("count"))); + } + catch (RetryException e) { + Throwable cause = e.getCause(); + if (cause instanceof DataAccessException ex) { + throw ex; + } + else { + throw new RuntimeException("Failed to retrieve next value of sequence", e); + } + } } @Override