Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring GraphQL batch loader tenant scope: never key a shared cache by row ID alone

Last updated: 1 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Batch loading reduces queries, but a loader that drops tenant identity or uses a cross-request cache can return another tenant's row.

Carry authorization into the batch

A GraphQL request contains parcel IDs 47 and 52. The batch fetch must still apply the authenticated tenant predicate; selecting by ID alone is a data leak even if the top-level parcel list was filtered. Spring's BatchLoaderRegistry creates DataLoaders per request and gives access to GraphQL context. That per-request cache is useful, but a separate application-wide cache keyed only by depot ID can cross tenant boundaries. Top-level authorization does not automatically secure nested data fetchers.

Choose the cache key

If depot IDs are tenant-local, include tenant ID in every cache and repository key. If IDs are globally unique, still enforce authorization at read time; uniqueness is not permission. A batch that merges parents from different GraphQL fields can carry distinct field arguments, so a simple @BatchMapping is insufficient when the nested field's filter changes by parent. Use the registry's context-aware loader in that case.

Test a collision

Create tenant A and tenant B with the same local depot number, query each as a separate principal, and verify the nested result stays within its tenant. Reverse request order to catch stale cache reuse. The repository call below shows the predicate; the surrounding loader and security context need an integration test.

Implementation sketch

Java
Map<Long, Depot> loadDepots(UUID tenantId, Set<Long> depotIds) {
    return depotRepository.findByTenantIdAndIdIn(tenantId, depotIds)
        .stream().collect(Collectors.toMap(Depot::id, Function.identity()));
}

Cost and verification

Tenant predicates add an index requirement on tenant ID plus row ID. Per-request caches cap data lifetime but still grow with query breadth; apply field and page limits.

Common Mistakes

  • Do not infer nested-field authorization from the root resolver's check.
  • Do not reuse a DataLoader cache across requests without tenant-aware keys.
  • Do not batch across differing field arguments as if every parent had the same filter.

Read next

Spring GraphQL @BatchMapping: collapse N+1 lookups without changing field behavior, Spring GraphQL tenant reads: scope every resolver before returning a record, Spring JdbcTemplate tenant predicates: put ownership in the SQL query, Spring method authorization: reject a cross-tenant read.

spring
spring-security
graphql-batch-tenant-scope
Storage details