Add GORM for MongoDB / Spring Data MongoDB interop module#15745
Add GORM for MongoDB / Spring Data MongoDB interop module#15745codeconsole wants to merge 10 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.
|
Hi @codeconsole, Please keep an eye on #15678, which modifies the way the MongoRegistry is being handled. It might be worth checking your diff against those updates, as this PR is downstream from 8.0.x-hibernate7. |
Introduces an optional module that lets GORM for MongoDB and Spring Data MongoDB run side by side over the same MongoClient, database and codecs, and - within a single transaction - the same MongoDB ClientSession. When the module and spring-data-mongodb are on the classpath of a Spring Boot application that has a GORM MongoDatastore, SpringDataMongoGormAutoConfiguration registers, over GORM's existing connection: a MongoDatabaseFactory bound to GORM's client and default database (which does not close that client), a MongoTemplate and MappingMongoConverter sharing the driver codec registry, and a primary transactionManager. That manager, GormSharedSessionMongoTransactionManager, extends GORM's DatastoreTransactionManager and binds GORM's ClientSession into Spring Data's thread-bound resources, so a GORM save() and a MongoTemplate/repository write in one @transactional method commit or roll back together. Unified transactions require GORM server-side transactions (grails.mongodb.transactional = true). The two object-mapping models stay separate; only the connection, codecs and session are shared.
36e36a5 to
342579f
Compare
jdaugherty
left a comment
There was a problem hiding this comment.
Whats the benefit for having Spring Data and GORM?
|
This is for apps that already have or need both, where today the only option is two separate Concrete cases: Spring-ecosystem libraries built on It's opt-in — a separate module that pulls in |
|
Thanks @borinquenkid — I checked #15678 against this PR's diff. It's Hibernate-only: the registry-scaling changes are all in |
…tx manager Move the Spring Boot auto-configuration registration from the non-standard META-INF/services/spring/ location to META-INF/spring/, which is where Spring Boot's ImportCandidates actually scans - otherwise the module never auto-configures in a real application. Add a test that asserts the imports file is at the canonical location (and not the old one) so the path cannot silently regress. Also document that the unified manager is @primary (required to win over GORM's mongoTransactionManager) and how to target the intended manager in an application that mixes another persistence stack.
Verifies the auto-configuration through an ApplicationContextRunner so the conditions, ordering and @ConditionalOnMissingBean back-off are actually exercised in a real context (the existing specs only invoked the @bean methods directly): the interop beans wire when a MongoDatastore is present, nothing activates without one, and an application-defined mongoTemplate takes precedence. Runs offline.
# Conflicts: # grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java
|
Isn't the transaction support a requirement before this PR? |
- setTimeout now throws TransactionUsageException for a non-default timeout instead of silently ignoring it. The server transaction is started before the manager applies a timeout, so it cannot be honored at this layer; DatastoreTransactionManager.doBegin catches it, rolls back the just-started ClientSession, and rethrows as CannotCreateTransactionException, so nothing leaks. - All nine AbstractMongoSession driver helpers branch on hasActiveTransaction() rather than a raw clientSession null check, so a session that lingers after its transaction commits falls back to the session-less overload. - Document on the MongoTransaction Javadoc that whole-transaction retry on TransientTransactionError is intentionally deferred: it requires re-executing the transaction body, which the Spring PlatformTransactionManager SPI cannot do. - Add a spec asserting a per-transaction timeout is rejected and the datastore stays usable afterwards, and document the timeout behavior in advancedConfig.adoc.
Add an interop test asserting that a timeout on GormSharedSessionMongoTransactionManager is refused (CannotCreateTransactionException) before any Spring Data resource holder is bound, that nothing persists on either stack, and that the manager stays usable afterward. This pins the ordering guarantee that super.doBegin runs — and can throw — before the ClientSession is shared with Spring Data.
Reword the MongoTransaction Javadoc so it no longer reads as deferred work: whole-transaction retry on a TransientTransactionError is left to the application (as Spring Data MongoDB's own transaction manager does), since re-running the transaction body would repeat its side effects.
Bring MongoTransaction and AbstractMongoSession in line with the reviewed Layer 1 code: MongoTransaction.commit() now explicitly aborts the server-side transaction on a failed commit rather than relying on close() to do so implicitly, and the AbstractMongoSession clientSession field carries its threading/nullability contract note. This makes the interop branch's copy identical to apache#15744.
✅ All tests passed ✅🏷️ Commit: a2217c9 Learn more about TestLens at testlens.app. |
Depends on #15744 (stacked PR)
This is stacked on top of #15744 (opt-in MongoDB transactions). Until #15744 merges, the diff here also includes its commit — please review/merge #15744 first, after which this PR reduces to just the new interop module. The unified transaction here relies on the
ClientSessionintroduced by #15744.What
A new opt-in module,
grails-data-mongodb-spring-data, that lets GORM for MongoDB and Spring Data MongoDB run side by side over the sameMongoClient, database and codecs — and, within a single@Transactionalmethod, the same MongoDB transaction.When the module and
spring-data-mongodbare on the classpath of a Spring Boot app that has a GORMMongoDatastore, it auto-configures over GORM's connection:MongoDatabaseFactorybound to GORM's client + default database (it does not close GORM's client),MongoTemplate(+MappingMongoConverter) sharing the driver codec registry,transactionManager(GormSharedSessionMongoTransactionManager) that binds GORM'sClientSessioninto Spring Data, so a GORMsave()and aMongoTemplate/repository write in one@Transactionalmethod commit or roll back together.Unified transactions require GORM server-side transactions (
grails.mongodb.transactional = true, from #15744). Spring Data repositories are enabled the usual way via@EnableMongoRepositories, on a package separate from GORM@Entityclasses.Boundary
Only the connection, codecs and (within a transaction) the
ClientSessionare shared; the two object-mapping models stay separate (GORM maps@Entity; Spring Data maps its own documents). Sharing the session reaches Spring Data's package-privateMongoResourceHolder, so a small helper lives in packageorg.springframework.data.mongodb— verified against the Spring Data MongoDB 5.x line in Spring Boot 4, class-path only (not JPMS module-path). A single flat transaction (PROPAGATION_REQUIRED) is supported;REQUIRES_NEW/NESTEDare not.Tests
UnifiedMongoTransactionSpec— GORM +MongoTemplatecommit and roll back together on one session; coexistence read; sequential transactions without session/holder leaks; a Spring Data repository over the shared connection; auto-config wiring; the shared factory does not close GORM's client.GormSpringDataSessionSupportSpec— coupling smoke test that fails loudly if the Spring Data internal shape changes.Targets
8.0.x.