I'm using Spring & graphql-java (graphql-java-annotation) in my project. For retrieving data part, i'm using a DataFetcher to get data from a service (from database).
The weird thing is that: myService
is always null. Anyone know the reason?
DataFetcher
@Component
public class MyDataFetcher implements DataFetcher {
// get data from database
@Autowired
private MyService myService;
@Override
public Object get(DataFetchingEnvironment environment) {
return myService.getData();
}
}
Schema
@Component
@GraphQLName("Query")
public class MyGraphSchema {
@GraphQLField
@GraphQLDataFetcher(MyDataFetcher.class)
public Data getData() {
return null;
}
}
MyService
@Service
public class MyService {
@Autowired
private MyRepository myRepo;
@Transactional(readOnly = true)
public Data getData() {
return myRepo.getData();
}
}
Main test
@Bean
public String testGraphql(){
GraphQLObjectType object = GraphQLAnnotations.object(MyGraphSchema.class);
GraphQLSchema schema = newSchema().query(object).build();
GraphQL graphql = new GraphQL(schema);
ExecutionResult result = graphql.execute("{getData {id name desc}}");;
Map<String, Object> v = (Map<String, Object>) result.getData();
System.out.println(v);
return v.toString();
}
Data fetchers are also known as “Resolvers” in many graphql implementations”. Resolver function is the place where graphql resolves the type or the field and gets its value from the configured resources like a database or other APIs or from cache etc and returns data back to user/caller.
GraphQL is a cross-platform, open-source, data query, and manipulation language for APIs. GraphQL servers are available for multiple languages such as Java, Python, C#, PHP, R, Haskell, JavaScript, Perl, Ruby, Scala, Go, Elixir, Erlang, and Clojure, etc. So, it can be used with any programming language and framework.
GraphQL is a relatively new concept from Facebook that's billed as an alternative to REST for Web APIs. In this tutorial, we'll learn how to set up a GraphQL server using Spring Boot, so that we can add it to existing applications or use it in new ones.
GraphQL SPQR (GraphQL Schema Publisher & Query Resolver, pronounced like speaker) is a simple-to-use library for rapid development of GraphQL APIs in Java.
Since in graphql-java-annotation the data fetcher is defined by annotation, it is constructed by the framework (using reflection to get the constructor), thus it can't be a bean.
The workaround I've found for this is setting it as ApplicationContextAware
, and then I can initialize some static field instead of a bean. Not the nicest thing, but it works:
@Component
public class MyDataFetcher implements DataFetcher, ApplicationContextAware {
private static MyService myService;
private static ApplicationContext context;
@Override
public Object get(DataFetchingEnvironment environment) {
return myService.getData();
}
@override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansExcepion {
context = applicationContext;
myService = context.getBean(MyService.class);
}
}
Basically you'll still get a new instance of the data fetcher initialized by graphQL, but also spring will initialize it, and since the myService
is static, you'll get the initialized one.
Though @Nir's approach works (and I often use it inside JPA Event Listeners), the DataFetcher objects are Singletons, so injecting via static properties is a little hacky.
However, GraphQL's execute
method allows you to pass in an object as a context, which will then be available in your DataFetchingEnvironment
object inside of your DataFetcher
(see the graphql.execute() line below):
@Component
public class GraphQLService {
@Autowired
MyService myService;
public Object getGraphQLResult() {
GraphQLObjectType object = GraphQLAnnotations.object(MyGraphSchema.class);
GraphQLSchema schema = newSchema().query(object).build();
GraphQL graphql = new GraphQL(schema);
ExecutionResult result = graphql.execute("{getData {id name desc}}", myService);
return result.getData();
}
}
public class MyDataFetcher implements DataFetcher {
@Override
public Object get(DataFetchingEnvironment environment) {
MyService myService = (MyService) environment.getContext();
return myService.getData();
}
}
The solution provided by @Nir Levy works perfectly. Just to make it a bit more reusable here. We can extract an abstract class which encapsulate the bean lookup logic and make autowiring work for its subclasses.
public abstract class SpringContextAwareDataFetcher implements DataFetcher, ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public final Object get(DataFetchingEnvironment environment) {
return applicationContext.getBean(this.getClass()).fetch(environment);
}
protected abstract Object fetch(DataFetchingEnvironment environment);
}
And the subclass can be like this:
@Component
public class UserDataFetcher extends SpringContextAwareDataFetcher {
@Autowired
private UserService userService;
@Override
public String fetch(DataFetchingEnvironment environment) {
User user = (User) environment.getSource();
return userService.getUser(user.getId()).getName();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With