Add GORM for MongoDB / Spring Data MongoDB interop module#15745
Add GORM for MongoDB / Spring Data MongoDB interop module#15745codeconsole wants to merge 5 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.
🚨 TestLens detected 6 failed tests 🚨Here is what you can do:
Test SummaryCI - Groovy Joint Validation Build / build_grails > :grails-data-mongodb-core:test
🏷️ Commit: 5887adb Test FailuresMongoTransactionSpec > test a REQUIRES_NEW inner transaction commits independently of a rolled back outer transaction (:grails-data-mongodb-core:test in CI - Groovy Joint Validation Build / build_grails)MongoTransactionSpec > test a committed transaction persists all writes atomically (:grails-data-mongodb-core:test in CI - Groovy Joint Validation Build / build_grails)MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction (:grails-data-mongodb-core:test in CI - Groovy Joint Validation Build / build_grails)MongoTransactionSpec > test a rolled back transaction discards all writes on the server (:grails-data-mongodb-core:test in CI - Groovy Joint Validation Build / build_grails)MongoTransactionSpec > test read-your-writes within an active transaction (:grails-data-mongodb-core:test in CI - Groovy Joint Validation Build / build_grails)MongoTransactionSpec > test writes across multiple collections roll back together (:grails-data-mongodb-core:test in CI - Groovy Joint Validation Build / build_grails)Muted TestsSelect tests to mute in this pull request:
Reuse successful test results:
Click the checkbox to trigger a rerun:
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.