matrei opened a new pull request, #16827:
URL: https://github.com/apache/grails-core/pull/16827

   ## Description
   
   Fixes #16801.
   
   In DATABASE mode, a `@Transactional` method of a class annotated with 
`@CurrentTenant` failed with a `TenantException` when called inside 
`Tenants.withId` while the tenant resolver resolves another tenant, as one that 
resolves the tenant from the web request does outside a request:
   
   ```groovy
   @CurrentTenant
   @Transactional
   class WidgetService {
   
       void save(String name) {
           new Widget(name: name).save(flush: true)
       }
   }
   
   Tenants.withId('test') {
       widgetService.save('widget')
   }
   // TenantException: Cannot resolve a single current tenant: [default, test] 
are bound on this thread for different datastores. ...
   ```
   
   These are two bugs from #16066, and a third with the same cause.
   
   ### `Tenants` and `@CurrentTenant` inside `Tenants.withId`
   
   `Tenants.withId(tenantId)` binds the tenant for the datastore of the default 
connection. Inside it, `Tenants.currentId()` and the other methods of `Tenants` 
that take no datastore, and a `@CurrentTenant` class that is not a data 
service, looked their datastore up through 
`GormApiResolver.findSingleDatastore()`. With more than one connection that 
resolved the current tenant, so in DATABASE mode it returned the datastore of 
the tenant's connection. No tenant is bound for that datastore, so they took 
the tenant the resolver resolves:
   
   ```groovy
   // DATABASE mode, a tenant resolver that resolves 'two'
   Tenants.withId('one') {
       Tenants.currentId() // 'two'
   }
   ```
   
   A `@CurrentTenant` method then bound that tenant for the datastore of `one`. 
The thread held two tenants, and a `@Transactional` method failed as above. 
With a resolver that resolves no tenant, the `@CurrentTenant` method failed 
with a `TenantNotFoundException`, and with a tenant the datastore has no 
connection for, with a `ConfigurationException`.
   
   `findSingleDatastore()` now returns the datastore of the default connection 
whenever one is registered, as on 7.x, where it returned the single datastore 
registered by type. Its callers all want that datastore: `Tenants`, 
`@CurrentTenant`, the fallback of `findServiceDatastore`, and the generated 
`getTargetDatastore()`, which calls `getDatastoreForConnection` on it. A 
`@CurrentTenant` data service was not affected, as `@CurrentTenant` looks its 
datastore up through `findServiceDatastore`.
   
   ### `Tenants.withId` with a domain class
   
   `Tenants.withId(domainClass, tenantId)` looked the datastore up through 
`findDatastore(domainClass)`, which in DATABASE mode resolves the current 
tenant as well, the one bound or the one the resolver resolves. It bound the 
given tenant for the datastore of that tenant, and failed:
   
   ```groovy
   // DATABASE mode, a tenant resolver that resolves 'two'
   Tenants.withId(Team, 'one') {
       new Team(name: 'Wolves').save(flush: true)
   }
   // ConfigurationException: No datastore found for connection named [one]
   ```
   
   It now binds the tenant for the datastore the domain class is mapped to, 
looked up through `findServiceDatastore`, as `Tenants.withTenant(domainClass, 
tenantId)` does too.
   
   ### The transaction manager of a tenant
   
   The `getTransactionManager()` that `@Transactional` and data services 
generate looked up the tenant with `CurrentTenantHolder.get()`, which throws a 
`TenantException` when different tenants are bound for different datastores. 
That is what the spec in the issue shows. It now calls 
`GormRegistry.findCurrentTenantTransactionManager`, which looks up the tenant 
bound for the default datastore, or for the datastore of the data service, and 
returns the transaction manager of the connection of that name, as before.
   
   The lookup moved from the generated code into `GormRegistry`. Classes 
compiled against 8.0.0 keep the unscoped lookup until they are recompiled. The 
change to `findSingleDatastore()` applies to them without that, and covers the 
case in the issue.
   
   ### Tests
   
   - `CurrentTenantTransformSpec` (`grails-datamapping-core-test`) has five new 
features, in DATABASE mode on GORM's in-memory datastore: for 
`Tenants.currentId()` and a `@CurrentTenant` method inside `Tenants.withId` 
while the resolver resolves another tenant, for a `@Transactional` method while 
another datastore has a different tenant, and for `Tenants.withId` with a 
domain class, at the top level and inside `Tenants.withId`. All fail without 
the change.
   - `DatabaseMultiTenantCurrentTenantTransactionalSpec` 
(`grails-data-hibernate5-core`, new) covers the case in the issue on GORM for 
Hibernate 5, with a `@CurrentTenant` `@Transactional` class and a resolver that 
resolves `default`. Both features fail without the change.
   - `GormApiResolverSpec` has two new features for `findSingleDatastore()`, 
with a tenant bound for the default datastore and with a session of another 
connection bound. Both fail without the change. The feature 
`findSingleDatastore delegates to findDatastore when more than one qualifier is 
registered` now says what it checks.
   - `GormRegistrySpec` has four features for 
`findCurrentTenantTransactionManager`.
   - `TransactionalTransformSpec` and `ServiceTransformSpec` have a new feature 
each for the generated `getTransactionManager()` while another datastore has a 
different tenant. Both fail without the change.
   
   These pass with the change:
   
   - all tests of `grails-datamapping-core`, `grails-datamapping-core-test`, 
`grails-data-simple`, `grails-datastore-web`, `grails-data-hibernate5-core`, 
`grails-data-hibernate7-core`, `grails-data-mongodb-core` and 
`grails-data-neo4j-core`;
   - `codeStyle` for `grails-datamapping-core` and 
`grails-data-hibernate5-core`.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to