英文:
Error passing optional argument in GraphQL-Spring Boot query
问题
我试图在查询中传递两个参数:
@QueryMapping
public List<Customer> getCustomers (@Argument OptionalInt start,
@Argument OptionalInt end
) {
List<Customer> customers = customerRepository.findAll();
if (start != null && end != null) {
try {
return customers.subList(start.getAsInt(), end.getAsInt());
} catch (IndexOutOfBoundsException e) {
return new LinkedList<>();
}
}
return customers;
}
但出现以下错误:
org.springframework.validation.BindException: org.springframework.graphql.data.GraphQlArgumentBinder$ArgumentsBindingResult: 1 errors
Field error in object 'optionalInt' on field '$': rejected value [0]; codes [typeMismatch.optionalInt,typeMismatch]; arguments []; default message [Failed to convert argument value]
尝试用Optional<Integer>
替换OptionalInt
会导致相同的错误。此外,如果尝试调用参数的isPresent()
方法而不是与null
进行比较,同样会出现相同的错误。事实上,与null
进行比较使我能够在不传递任何参数的情况下进行查询。
客户类型:
type Customer {
dni: ID!
name: String!
surname: String!
accounts: [Account!]!
}
查询类型:
type Query {
getCustomers(start: Int, end: Int): [Customer!]!
getCustomer(dni: String!): Customer
}
英文:
I'm trying to pass two arguments in a query:
@QueryMapping
public List<Customer> getCustomers (@Argument OptionalInt start,
@Argument OptionalInt end
) {
List<Customer> customers = customerRepository.findAll();
if (start != null && end != null) {
try {
return customers.subList(start.getAsInt(), end.getAsInt());
} catch (IndexOutOfBoundsException e) {
return new LinkedList<>();
}
}
return customers;
}
But this error is thrown:
org.springframework.validation.BindException: org.springframework.graphql.data.GraphQlArgumentBinder$ArgumentsBindingResult: 1 errors
Field error in object 'optionalInt' on field '$': rejected value [0]; codes [typeMismatch.optionalInt,typeMismatch]; arguments []; default message [Failed to convert argument value]
at org.springframework.graphql.data.GraphQlArgumentBinder.bind(GraphQlArgumentBinder.java:151) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.data.method.annotation.support.ArgumentMethodArgumentResolver.resolveArgument(ArgumentMethodArgumentResolver.java:71) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:81) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.getMethodArgumentValues(DataFetcherHandlerMethod.java:177) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.invoke(DataFetcherHandlerMethod.java:119) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.invoke(DataFetcherHandlerMethod.java:107) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer$SchemaMappingDataFetcher.get(AnnotatedControllerConfigurer.java:680) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.graphql.execution.ContextDataFetcherDecorator.lambda$get$0(ContextDataFetcherDecorator.java:88) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at io.micrometer.context.ContextSnapshot.lambda$wrap$1(ContextSnapshot.java:92) ~[context-propagation-1.0.2.jar!/:1.0.2]
at org.springframework.graphql.execution.ContextDataFetcherDecorator.get(ContextDataFetcherDecorator.java:88) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at graphql.execution.ExecutionStrategy.invokeDataFetcher(ExecutionStrategy.java:309) ~[graphql-java-20.2.jar!/:na]
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:286) ~[graphql-java-20.2.jar!/:na]
at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:212) ~[graphql-java-20.2.jar!/:na]
at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:55) ~[graphql-java-20.2.jar!/:na]
at graphql.execution.Execution.executeOperation(Execution.java:161) ~[graphql-java-20.2.jar!/:na]
at graphql.execution.Execution.execute(Execution.java:104) ~[graphql-java-20.2.jar!/:na]
at graphql.GraphQL.execute(GraphQL.java:557) ~[graphql-java-20.2.jar!/:na]
at graphql.GraphQL.lambda$parseValidateAndExecute$11(GraphQL.java:476) ~[graphql-java-20.2.jar!/:na]
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:471) ~[graphql-java-20.2.jar!/:na]
at graphql.GraphQL.executeAsync(GraphQL.java:439) ~[graphql-java-20.2.jar!/:na]
at org.springframework.graphql.execution.DefaultExecutionGraphQlService.lambda$execute$2(DefaultExecutionGraphQlService.java:82) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:47) ~[reactor-core-3.5.6.jar!/:3.5.6]
at reactor.core.publisher.Mono.subscribe(Mono.java:4485) ~[reactor-core-3.5.6.jar!/:3.5.6]
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4551) ~[reactor-core-3.5.6.jar!/:3.5.6]
at reactor.core.publisher.Mono.toFuture(Mono.java:5063) ~[reactor-core-3.5.6.jar!/:3.5.6]
at org.springframework.core.ReactiveAdapterRegistry$ReactorRegistrar.lambda$registerAdapters$5(ReactiveAdapterRegistry.java:244) ~[spring-core-6.0.9.jar!/:6.0.9]
at org.springframework.core.ReactiveAdapter.fromPublisher(ReactiveAdapter.java:121) ~[spring-core-6.0.9.jar!/:6.0.9]
at org.springframework.web.servlet.function.DefaultAsyncServerResponse.create(DefaultAsyncServerResponse.java:186) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at org.springframework.web.servlet.function.ServerResponse.async(ServerResponse.java:250) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at org.springframework.graphql.server.webmvc.GraphQlHttpHandler.handleRequest(GraphQlHttpHandler.java:111) ~[spring-graphql-1.2.0.jar!/:1.2.0]
at org.springframework.web.servlet.function.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:107) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.9.jar!/:6.0.9]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.9.jar!/:6.0.9]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.9.jar!/:6.0.9]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.9.jar!/:6.0.9]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.9.jar!/:6.0.9]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.9.jar!/:6.0.9]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.9.jar!/:6.0.9]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.8.jar!/:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.8.jar!/:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Trying to replace the OptionalInt
for an Optional<Integer>
results in the same error. Furthermore, if I try to invoke the arguments' isPresent()
method instead of comparing them with null
, the same error shows up. In fact, comparing them with null
allows me to make the query without passing any parameters.
Customer type:
type Customer {
dni: ID!
name: String!
surname: String!
accounts: [Account!]!
}
Query type:
type Query {
getCustomers(start: Int, end: Int): [Customer!]!
getCustomer(dni: String!): Customer
}
答案1
得分: 1
无论数据映射是从数据库、graphql、json反序列化还是任何一种数据类型转换成另一种(Java对象本质上也只是一组字节),都会存在类型转换的限制。
Optional<T> 或者 OptionalInt 或者任何类似的包装类在你的使用情况中都不受支持。
此外,将可选参数作为方法参数传递或者将这些类型用作类成员可能是代码味道的迹象,因为它们容易引发错误的使用。
你的代码示例正好说明了我所说的:对可选参数进行空值检查并不会使这个方法更安全,因为包装器仍然可以为空,并且 getAsInt()
会抛出空指针异常。
只需使用普通的 Integer。希望有所帮助。
英文:
Regardless of what data mapping takes place - be it data from a database, graphql, json deserialization or any transformation of one type of data into another (Java objects are also just a set of bytes), there are always restrictions on the convertibility of types.
Optional<T> or OptionalInt or any other similar wrappers is not supported as you can see in your use case.
To add more, passing optional as method parameter or using these types as class member can be a sign of code smell due to its error prone usage.
Your code example contains exactly what I said: null checks on the Optional params doesn't make this method secure, since wrapper still can be empty, and getAsInt()
will throw NullPointerException.
Just use regular Integer. Hope it helps
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论