Skip to content

Commit fb636ad

Browse files
committed
Support maxBatchSize via BatchMapping annotation
Closes gh-520
1 parent 1eca677 commit fb636ad

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/BatchMapping.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,4 +95,12 @@
9595
*/
9696
String typeName() default "";
9797

98+
/**
99+
* Set the maximum number of keys to include a single batch, before
100+
* splitting into multiple batches of keys to load.
101+
* <p>By default this is -1 in which case there is no limit.
102+
* @since 1.1
103+
*/
104+
int maxBatchSize() default -1;
105+
98106
}

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,11 +309,11 @@ private MappingInfo getMappingInfo(Method method, Object handler, Class<?> handl
309309
String typeName;
310310
String field;
311311
boolean batchMapping = false;
312+
int batchSize = -1;
312313
HandlerMethod handlerMethod = createHandlerMethod(method, handler, handlerType);
313314

314315
Annotation annotation = annotations.iterator().next();
315-
if (annotation instanceof SchemaMapping) {
316-
SchemaMapping mapping = (SchemaMapping) annotation;
316+
if (annotation instanceof SchemaMapping mapping) {
317317
typeName = mapping.typeName();
318318
field = (StringUtils.hasText(mapping.field()) ? mapping.field() : method.getName());
319319
}
@@ -322,6 +322,7 @@ private MappingInfo getMappingInfo(Method method, Object handler, Class<?> handl
322322
typeName = mapping.typeName();
323323
field = (StringUtils.hasText(mapping.field()) ? mapping.field() : method.getName());
324324
batchMapping = true;
325+
batchSize = mapping.maxBatchSize();
325326
}
326327

327328
if (!StringUtils.hasText(typeName)) {
@@ -354,7 +355,7 @@ private MappingInfo getMappingInfo(Method method, Object handler, Class<?> handl
354355
"No parentType specified, and a source/parent method argument was also not found: " +
355356
handlerMethod.getShortLogMessage());
356357

357-
return new MappingInfo(typeName, field, batchMapping, handlerMethod);
358+
return new MappingInfo(typeName, field, batchMapping, batchSize, handlerMethod);
358359
}
359360

360361
private HandlerMethod createHandlerMethod(Method method, Object handler, Class<?> handlerType) {
@@ -394,11 +395,16 @@ private String registerBatchLoader(MappingInfo info) {
394395
Class<?> clazz = returnType.getParameterType();
395396
Class<?> nestedClass = (clazz.equals(Callable.class) ? returnType.nested().getNestedParameterType() : clazz);
396397

398+
BatchLoaderRegistry.RegistrationSpec<Object, Object> registration = registry.forName(dataLoaderKey);
399+
if (info.getMaxBatchSize() > 0) {
400+
registration.withOptions(options -> options.setMaxBatchSize(info.getMaxBatchSize()));
401+
}
402+
397403
if (clazz.equals(Flux.class) || Collection.class.isAssignableFrom(nestedClass)) {
398-
registry.forName(dataLoaderKey).registerBatchLoader(invocable::invokeForIterable);
404+
registration.registerBatchLoader(invocable::invokeForIterable);
399405
}
400406
else if (clazz.equals(Mono.class) || nestedClass.equals(Map.class)) {
401-
registry.forName(dataLoaderKey).registerMappedBatchLoader(invocable::invokeForMap);
407+
registration.registerMappedBatchLoader(invocable::invokeForMap);
402408
}
403409
else {
404410
throw new IllegalStateException("@BatchMapping method is expected to return " +
@@ -434,12 +440,18 @@ private static class MappingInfo {
434440

435441
private final boolean batchMapping;
436442

443+
private final int maxBatchSize;
444+
437445
private final HandlerMethod handlerMethod;
438446

439-
public MappingInfo(String typeName, String field, boolean batchMapping, HandlerMethod handlerMethod) {
447+
public MappingInfo(
448+
String typeName, String field, boolean batchMapping, int maxBatchSize,
449+
HandlerMethod handlerMethod) {
450+
440451
this.coordinates = FieldCoordinates.coordinates(typeName, field);
441-
this.handlerMethod = handlerMethod;
442452
this.batchMapping = batchMapping;
453+
this.maxBatchSize = maxBatchSize;
454+
this.handlerMethod = handlerMethod;
443455
}
444456

445457
public FieldCoordinates getCoordinates() {
@@ -451,6 +463,10 @@ public boolean isBatchMapping() {
451463
return this.batchMapping;
452464
}
453465

466+
public int getMaxBatchSize() {
467+
return this.maxBatchSize;
468+
}
469+
454470
public HandlerMethod getHandlerMethod() {
455471
return this.handlerMethod;
456472
}

spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingDetectionTests.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.graphql.data.method.annotation.support;
1717

18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collections;
1821
import java.util.List;
1922
import java.util.Map;
2023
import java.util.concurrent.Callable;
@@ -23,6 +26,7 @@
2326
import graphql.schema.DataFetcher;
2427
import graphql.schema.idl.RuntimeWiring;
2528
import org.dataloader.BatchLoaderEnvironment;
29+
import org.dataloader.DataLoader;
2630
import org.dataloader.DataLoaderRegistry;
2731
import org.junit.jupiter.api.Test;
2832
import reactor.core.publisher.Flux;
@@ -71,6 +75,22 @@ void registerWithDefaultCoordinates() {
7175
"Book.authorCallableMap", "Book.authorEnvironment");
7276
}
7377

78+
@Test
79+
void registerWithMaxBatchSize() {
80+
81+
BatchSizeController controller = new BatchSizeController();
82+
initRuntimeWiringBuilder(controller).build();
83+
DataLoaderRegistry registry = new DataLoaderRegistry();
84+
this.batchLoaderRegistry.registerDataLoaders(registry, GraphQLContext.newContext().build());
85+
86+
DataLoader<Integer, String> dataLoader = registry.getDataLoader("Book.authors");
87+
dataLoader.loadMany(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
88+
dataLoader.dispatchAndJoin();
89+
90+
assertThat(controller.getBatchSizes()).containsExactly(5, 3);
91+
92+
}
93+
7494
@Test
7595
void invalidReturnType() {
7696
assertThatThrownBy(() -> initRuntimeWiringBuilder(InvalidReturnTypeController.class).build())
@@ -83,9 +103,15 @@ void schemaAndBatch() {
83103
.hasMessageStartingWith("Expected either @BatchMapping or @SchemaMapping, not both");
84104
}
85105

86-
private RuntimeWiring.Builder initRuntimeWiringBuilder(Class<?> handlerType) {
106+
@SuppressWarnings("unchecked")
107+
private <T> RuntimeWiring.Builder initRuntimeWiringBuilder(T handler) {
87108
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
88-
context.registerBean(handlerType);
109+
if (handler instanceof Class<?> handlerType) {
110+
context.registerBean(handlerType);
111+
}
112+
else {
113+
context.registerBean((Class<T>) handler.getClass(), () -> handler);
114+
}
89115
context.registerBean(BatchLoaderRegistry.class, () -> this.batchLoaderRegistry);
90116
context.refresh();
91117

@@ -136,6 +162,24 @@ public List<Author> authorEnvironment(BatchLoaderEnvironment environment, List<B
136162
}
137163

138164

165+
@Controller
166+
@SuppressWarnings("unused")
167+
private static class BatchSizeController {
168+
169+
private final List<Integer> batchSizes = new ArrayList<>();
170+
171+
public List<Integer> getBatchSizes() {
172+
return this.batchSizes;
173+
}
174+
175+
@BatchMapping(maxBatchSize = 5, typeName = "Book")
176+
public List<String> authors(List<Integer> bookIds) {
177+
this.batchSizes.add(bookIds.size());
178+
return Collections.emptyList();
179+
}
180+
}
181+
182+
139183
@Controller
140184
private static class InvalidReturnTypeController {
141185

0 commit comments

Comments
 (0)