Skip to content

Add opt-in MongoDB multi-document transactions to GORM for MongoDB#15744

Open
codeconsole wants to merge 2 commits into
apache:8.0.xfrom
codeconsole:feat/mongo-server-transactions
Open

Add opt-in MongoDB multi-document transactions to GORM for MongoDB#15744
codeconsole wants to merge 2 commits into
apache:8.0.xfrom
codeconsole:feat/mongo-server-transactions

Conversation

@codeconsole

Copy link
Copy Markdown
Contributor

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 real ClientSession transaction, so all reads and writes commit or roll back atomically:

Person.withTransaction {
    new Person(name: "Fred").save()
    new Person(name: "Wilma").save()
    // both commit together, or neither if an exception is thrown
}

How

  • New MongoTransaction (replaces the flush-only SessionOnlyTransaction when enabled): commit() flushes then commitTransaction() (with bounded retry on UnknownTransactionCommitResult), rollback() aborts; both close the session. On commit failure the GORM session cache is cleared.
  • AbstractMongoSession holds the active ClientSession, starts it in beginTransactionInternal(), and routes every read/write through small helpers that pass the session when a transaction is active and stay session-less otherwise.
  • The session is threaded through both session engines, both persisters, MongoQuery, and the MongoStaticApi/MongoEntity surface.
  • Core DatastoreTransactionManager is unchanged — it already orchestrates flush/commit/rollback; this just supplies a Transaction that drives a server transaction.

Opt-in and fallback

  • Default is off (grails.mongodb.transactional defaults to false) — no behavior change for existing apps.
  • Requires a replica set or sharded cluster. If a standalone topology is detected, the feature is disabled with a one-time warning and GORM falls back to the legacy flush behavior.

Boundaries

  • Identifier generation (the native Long counter) is intentionally left non-transactional, mirroring database sequence semantics.
  • Like GORM's transaction manager generally, a single flat transaction (PROPAGATION_REQUIRED) is supported; REQUIRES_NEW/NESTED are not.

Tests

  • MongoTransactionSpec — commit persists multiple docs; rollback discards them on the server; read-your-writes within a transaction; cross-collection atomic rollback; findOneAndDelete participates in the transaction; nested GORM REQUIRES_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.

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 borinquenkid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@testlens-app

testlens-app Bot commented Jun 18, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

⚠️ TestLens detected flakiness ⚠️

Test Summary

CI / Functional Tests (Java 21, indy=false) > :grails-test-examples-scaffolding:integrationTest

Test Runs Flakiness
UserControllerSpec > User list ❌ ✅ 🚫 🚫

CI / Functional Tests (Java 25, indy=false) > :grails-test-examples-scaffolding:integrationTest

Test Runs Flakiness
UserControllerSpec > User list ❌ ✅ 🚫 🚫

🏷️ Commit: 59d51fd
▶️ Tests: 9506 executed
⚪️ Checks: 44/44 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants