Add opt-in MongoDB multi-document transactions to GORM for MongoDB#15744
Add opt-in MongoDB multi-document transactions to GORM for MongoDB#15744codeconsole wants to merge 4 commits into
Conversation
GORM for MongoDB previously treated a transaction as a client-side flush boundary: pending writes were batched and flushed on commit, but each write auto-committed individually and nothing rolled back when a later operation failed. This adds real server-side transactions backed by a com.mongodb.client.ClientSession. When grails.mongodb.transactional is enabled (default false), a GORM transaction starts a ClientSession and MongoDB transaction and every read and write for the session runs within it, committing or aborting atomically. A new MongoTransaction drives the commit (retrying on an UnknownTransactionCommitResult) and the abort, and closes the session afterwards. The feature is opt-in and degrades gracefully: a standalone topology is detected at runtime and falls back to the legacy flush-only behavior with a one-time warning. Identifier generation for native Long ids is intentionally left non-transactional, mirroring the semantics of database sequences.
borinquenkid
left a comment
There was a problem hiding this comment.
Hi @codeconsole,
Please keep an eye on #15678 (GORM: Shared Mapping Registry O(M+N) Scaling), which introduces significant internal structural refactoring to how GormRegistry and MongoDatastore handle tenant routing and fallback resolution.
Since that optimization is targeting 8.0.x-hibernate7, your transaction changes here will be downstream from those modifications. It might be worth checking your diff against those updates to prevent initialization order regressions or multi-tenant signature mismatches when merging into the 8.0 release line.
…ve Long id tests On a failed commit (or a flush failure during commit), MongoTransaction now explicitly aborts the server transaction rather than relying on ClientSession.close() to abort it implicitly. Adds tests covering native Long identifier generation inside a transaction: ids are generated and persisted on commit, and the document is rolled back on failure even though the id counter is intentionally not enrolled in the transaction.
…ined The clientSession field is accessed only on the owning session's thread (per the AbstractSession single-thread-confinement contract), so it needs no synchronization. Adds a clarifying comment; no behavior change.
🚨 TestLens detected 8 failed tests 🚨Here is what you can do:
Test SummaryCI - Groovy Joint Validation Build / build_grails > :grails-data-mongodb-core:test
🏷️ Commit: 9993091 Muted TestsNote Checks are currently running using the configuration below. Select tests to mute in this pull request: 🔲 MongoTransactionSpec > test a REQUIRES_NEW inner transaction commits independently of a rolled back outer transaction Reuse successful test results: 🔲 ♻️ Only rerun the tests that failed or were muted before Click the checkbox to trigger a rerun: ☑️ Rerun jobs Learn more about TestLens at testlens.app. |
What
Adds opt-in MongoDB multi-document transactions to GORM for MongoDB.
Previously GORM for MongoDB never used a
com.mongodb.client.ClientSession: every driver write was issued session-less and auto-committed, so a GORM transaction was only a client-side flush boundary — writes already flushed within it were not rolled back on failure (no server-side atomicity).With
grails.mongodb.transactional = true, a GORM transaction now starts and drives a realClientSessiontransaction, so all reads and writes commit or roll back atomically:How
MongoTransaction(replaces the flush-onlySessionOnlyTransactionwhen enabled):commit()flushes thencommitTransaction()(with bounded retry onUnknownTransactionCommitResult),rollback()aborts; both close the session. On commit failure the GORM session cache is cleared.AbstractMongoSessionholds the activeClientSession, starts it inbeginTransactionInternal(), and routes every read/write through small helpers that pass the session when a transaction is active and stay session-less otherwise.MongoQuery, and theMongoStaticApi/MongoEntitysurface.DatastoreTransactionManageris unchanged — it already orchestrates flush/commit/rollback; this just supplies aTransactionthat drives a server transaction.Opt-in and fallback
grails.mongodb.transactionaldefaults tofalse) — no behavior change for existing apps.Boundaries
Longcounter) is intentionally left non-transactional, mirroring database sequence semantics.PROPAGATION_REQUIRED) is supported;REQUIRES_NEW/NESTEDare not.Tests
MongoTransactionSpec— commit persists multiple docs; rollback discards them on the server; read-your-writes within a transaction; cross-collection atomic rollback;findOneAndDeleteparticipates in the transaction; nested GORMREQUIRES_NEW.MongoTransactionDisabledSpec— default-off keeps the legacy flush behavior.Targets
8.0.x. Independent of #15743; this is also the prerequisite for a follow-up Spring Data MongoDB interop module.