Entering a bean in a DataFetcher from GraphQL

I am using Spring and graphql-java ( graphql-java-annotation ) in my project. To retrieve a piece of data, I use a DataFetcher to retrieve data from a service (from a database).

The strange thing is that myService always null. Does 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(); } } 

Scheme

 @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(); } 
+5
source share
3 answers

Since in the graphql-java annotation the data collector is defined by the annotation, it is built by the framework (using reflection to get the constructor), so it cannot bean.

The workaround I found for this is setting it as ApplicationContextAware , and then I can initialize some static field instead of 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 still get a new instance of the data collector, initialized by the chart, but spring also initializes it, and since myService is static, you get initialized.

+5
source

The solution provided by @Nir Levy works great. Just to make it more reusable here. We can extract an abstract class that encapsulates the bean search logic and does the auto-preparation 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 could 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(); } } 
0
source

Although the @Nir approach works (and I often use it inside JPA event listeners), the DataFetcher objects are Singletons, so injecting through static properties is a little crack.

However, the GraphQL execute method allows you to pass an object as a context, which will then be available in your DataFetchingEnvironment object inside your DataFetcher (see 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(); } } 
0
source

Source: https://habr.com/ru/post/1262204/


All Articles