I’m trying to check that mongodb does behave the way I expect it to behave and lock a document when it’s modified in a transaction.
I’m using
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version>
and spring-data with a replicaset of
mongo 7.0.5 community
My goal is to do the following:
- I create a transaction
- I “lock” a document by writing to it
- Another process tries to write on the same document
- This second process “write” shall either hang until the transaction is comited or be refused because of a WriteConflict (I’d rather it to be refused)
I’m using this code :
@Getter
@Setter
class DummyDocument {
@MongoId
private String uuid;
private String lock;
private List<String> simpleCollection = new ArrayList<>();
public DummyDocument(String uuid) {
this.uuid = uuid;
}
}
public class ConcurrentDocumentAccess {
@Autowired
private MongoClient client;
@Autowired
private MongoTemplate mongoTemplate;
@After
public void cleanup() {
DummyDocument doc = mongoTemplate.findById("test", DummyDocument.class);
mongoTemplate.remove(doc);
}
@Test
public void documentLockingTest() {
// Create a document
DummyDocument doc = new DummyDocument("test");
mongoTemplate.save(doc);
// A query to find the doc
Query findDoc = new Query().addCriteria(Criteria.where("uuid").is("test"));
// An update to change the lock value in the doc
Update lock = new Update().set("lock", "locked");
// An update of the doc
Update updateCollection = new Update().addToSet("simpleCollection", "something");
try (ClientSession session = client.startSession()) {
session.startTransaction();
// Acquire lock on doc by writing on it
UpdateResult lockResult = mongoTemplate.withSession(session).updateFirst(findDoc, lock,
DummyDocument.class);
assertThat(lockResult.getModifiedCount() == 1L).isTrue();
// Try to update the collection out of the transaction
UpdateResult changeResult = mongoTemplate.updateFirst(findDoc, updateCollection, DummyDocument.class);
// assertThat(changeResult.getModifiedCount() == 0L).isTrue();
session.commitTransaction();
} catch (MongoCommandException e) {
e.printStackTrace();
}
DummyDocument updatedDoc = mongoTemplate.findOne(findDoc, DummyDocument.class);
assertThat(updatedDoc.getLock()).isEqualTo("locked");
assertThat(updatedDoc.getSimpleCollection()).doesNotContain("something");
}
}
What I observe is that the query outside the transaction is forced on the document, then the transaction hangs until it fails with a response : {"errorLabels": ["TransientTransactionError"], "ok": 0.0, "errmsg": "Transaction with { txnNumber: 2 } has been aborted.", "code": 251, "codeName": "NoSuchTransaction"
Is there something I’m doing wrong or is it the expected behavior ?