Can a graphql mutation be wired inside a DataFetcher class along with query? - graphql

Error:FieldUndefined: Field 'createUser' in type 'Query' is undefined # 'createUser'"
#Service
public class GraphQlService {
#Value("classpath:schema.graphql")
Resource resource;
private GraphQL graphQL;
#Autowired
UserDataFetcher userFetcher;
#Autowired
PostDataFetcher postFetcher;
#PostConstruct
public void loadSchema() throws IOException {
File schemaFile = resource.getFile();
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
graphQL = GraphQL.newGraphQL(schema).build();
}
private RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.type("Query", typeWiring -> typeWiring
.dataFetcher("user", userFetcher)
.dataFetcher("post", postFetcher))
.type("Mutation", typeWiring -> typeWiring
.dataFetcher("createUser", userFetcher))
.build();
}
public GraphQL getGraphQL() {
return graphQL;
}
}
1. Cant I use common datafetcher/reslover for both Query and Mutation
as I have done below in a single class.It is not able to find
createUser?
#Component
public class UserDataFetcher implements DataFetcher<List<User>> {
#Autowired
UserRepository userRepository;
public User createUser(DataFetchingEnvironment environment) {
String username = environment.getArgument("username");
String location= environment.getArgument("location");
User[] follower = environment.getArgument("followers");
User[] following = environment.getArgument("following");
Post[] pos = environment.getArgument("posts");
User user = new User();
user.setUsername(username);
user.setFollowers(follower);
user.setFollowing(following);
user.setLocation(location);
user.setPosts(pos);
return userRepository.save(user);
}
#Override
public List<User> get(DataFetchingEnvironment environment) {
return userRepository.findAll();
}
}
//SDL below for schema
schema {
query: Query
mutation: Mutation
}
type User{
id:ID!
username:String!
followers:[User]
following:[User]
location:String!
posts:[Posts]
}
type Post{
id: ID
title: String!
author: User!
}
type Query{
user: [User]
post: [Post]
}
type Mutation{
createUser(username: String!, location: String!,followers: [User],following:[User],posts:[Post]):User
}
2. Is the schema correct because it would say User and Post are not mentioned as InputType. I tried InputType for User and Post but
couldnt get it working.How should correct schema for storing
followers and following look like ?

1) No, each DataFetcher implementation can do a single operation only - that's the get method the interface specifies. How would the framework know to call the createUser method?
2) No, the schema is not valid. You can not use User as an argument (input) type. You need to define a separate input type, e.g.
input UserInput {
username:String!
followers:[ID!]
following:[ID!]
location:String!
}
Just think, if you could make User the input, how would you provide the mandatory ID? What if User implements an interface, since interfaces are not defined for inputs? Imagine if User had a recursive field type. In output, that would be fine (as you control the nesting level directly), but would be impossible to satisfy for an input type.
The rules for inputs and output are quite different, so the types need to be defined separately.
Also, in this example, only the IDs are optionally expected for the followers or the followed, instead of the full objects, as it often makes more sense.

Related

SpringBoot GraphQL mutation giving error No Root resolvers for query type 'Query' found

Am getting the error No Root resolvers for query type 'Query' found when using mutation in SpringBoot with GraphQL. The queries are working fine , but on adding the GraphQLMutationResolver, it is giving the error on Spring Boot startup.
Kindly advise.
.graphqls file
type Query {
allBooks: [Book]
getBookByIsn(isn: Int): Book
allPublishers: [Publisher]
}
type Book {
isn: Int
title: String
author: String
publishedDate: String
publisher: Publisher!
}
type Publisher {
pId : Int
publisherName: String
address: String
}
input CreatePublisher {
pId : Int
publisherName: String
address: String
}
type Mutation {
addPublisher(input: CreatePublisher!): Publisher
}
Mutation Resolver
#Component
public class PublisherMutation implements GraphQLMutationResolver{
#Autowired
private PublisherRepository publisherRepository;
#Transactional
public Publisher addPublisher(CreatePublisher createPublisher ) {
Publisher publisher = new Publisher(createPublisher.getPId(), createPublisher.getPublisherName(), createPublisher.getAddress());
publisherRepository.save(publisher);
return publisher;
}
}
Done, solved by adding a newTypeWiring of type Mutation for Create

Best approach to create different JSON response from same Entity

I have an entity class User with 20 fields, some of them being confidential fields. I have a controller class, which has a method getUser to fetch all the user from DB and send the JSON respone. Below is the sample code for the same:
#GetMapping("/getUsers")
public UserDT getUsers( Model theModel) {
List<User> userList;
userList = userService.findAll();
return userList;
}
When I run the above code, it returns all the fields from User table/User Entity Class. Instead of sending all the fields, I would like to send selected fields say Field1 to Field5 only.
Ultimate goal is to have multiple views for the same Entity Class. For URL1 I would like to show only field1 to field5 of User table, But for URL2 I would like to show Field9 , Filed15, Field20.
Do I need to create multiple Entity Class for each URL? Please guide me with the best practice to be followed in such scenario.
Assuming you are using Spring Data JPA, use projections.
So create different projections for your different URLs write a method that returns the projection (or a dynamic one as in the documentation).
public interface NamesOnlyProjection {
String getFirstName();
String getLastName();
}
public interface UserinfoProjection {
String getUsername();
String getPassword();
String getDepartment();
}
Then in your repository do something like this
public interface PersonRepository extends JpaRepository<Person, Long> {
<T> List<T> findAll(Class<T> type);
}
Then you can do something like this in your controller/service
#RestController
public class PersonController {
private final PersonRepository persons;
#GetMapping("/people/names")
public List<NamesOnlyProjection> allNames() {
return persons.findAll(NamesOnlyProjection.class);
}
#GetMapping("/people/users")
public List<UserinfoProjection> allNames() {
return persons.findAll(UserinfoProjection.class);
}
}

Return type of Query function in Android Room Database?

I have a simple question but I just can't find any documentation anywhere.
Suppose we have the below example, which can be found here.
#Dao
public interface UserDao {
#Query("SELECT * FROM User WHERE userId = :id")
public User getById(int id);
}
I notice the return type of the function is User, but what happens if there query doesn't return a result? (i.e. there is no user in the table with the specified id)
Will the function simply return null?
Also, if this were a Kotlin function, should the signature be the following with nullable type User?
#Query("SELECT * FROM User WHERE userId = :id")
fun getById(int id): User?
When documentation is not enough, reading sources could help.
Whatever you use (Java or Kotlin) describing Dao interface - Dao class implementation in Java (generated by Room under the hood) would be the same:
public User getById(int id) {
...
final Cursor _cursor = DBUtil.query(__db, "SELECT * FROM User ...", false, null);
.....
if(_cursor.moveToFirst()) {
// If there is at least one record
_result = new User(<...User's fields gotten from query...>);
} else {
// If there is no record
_result = null; // <---- here it is!
}
return _result;
...
}
Also, if this were a Kotlin function, should the signature be the following with nullable type User?
Technically you could omit ?, but it could mislead in reading code, so better use User? explicitly.

Advantages of interface implements in GraphQL

I'm new to GraphQL.
// using interface
interface User {
name: String
}
type UserDetail implements User {
name: String
email: String
}
// just type
type User {
name: String
}
type UserDetail {
name: String
email: String
}
There is duplicated field name in interface User and type UserDetail.
I thought UserDetail doesn't have to include name.
I couldn't find detailed information of interface.
Why do I use interface, implements instead of just type?
And I want to know differences of using interface and type, advantages of using interface.
Please comment any advices.

Spring Data JPA. How to get only a list of IDs from findAll() method

I have a very complicated model. Entity has a lot relationship and so on.
I try to use Spring Data JPA and I prepared a repository.
but when I invoke a method findAll() with specification for the object a have a performance issue because objects are very big. I know that because when I invoke a method like this:
#Query(value = "select id, name from Customer ")
List<Object[]> myFindCustomerIds();
I didn't have any problems with performance.
But when I invoke
List<Customer> findAll();
I had a big problem with performance.
The problem is that I need to invoke findAll method with Specifications for Customer that is why I cannot use method which returns a list of arrays of objects.
How to write a method to finding all customers with specifications for Customer entity but which returns only an IDs.
like this:
List<Long> findAll(Specification<Customer> spec);
I cannot use in this case pagination.
Please help.
Why not using the #Query annotation?
#Query("select p.id from #{#entityName} p")
List<Long> getAllIds();
The only disadvantage I see is when the attribute id changes, but since this is a very common name and unlikely to change (id = primary key), this should be ok.
This is now supported by Spring Data using Projections:
interface SparseCustomer {
String getId();
String getName();
}
Than in your Customer repository
List<SparseCustomer> findAll(Specification<Customer> spec);
EDIT:
As noted by Radouane ROUFID Projections with Specifications currently doesn't work beacuse of bug.
But you can use specification-with-projection library which workarounds this Spring Data Jpa deficiency.
I solved the problem.
(As a result we will have a sparse Customer object only with id and name)
Define their own repository:
public interface SparseCustomerRepository {
List<Customer> findAllWithNameOnly(Specification<Customer> spec);
}
And an implementation (remember about suffix - Impl as default)
#Service
public class SparseCustomerRepositoryImpl implements SparseCustomerRepository {
private final EntityManager entityManager;
#Autowired
public SparseCustomerRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public List<Customer> findAllWithNameOnly(Specification<Customer> spec) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleQuery = criteriaBuilder.createTupleQuery();
Root<Customer> root = tupleQuery.from(Customer.class);
tupleQuery.multiselect(getSelection(root, Customer_.id),
getSelection(root, Customer_.name));
if (spec != null) {
tupleQuery.where(spec.toPredicate(root, tupleQuery, criteriaBuilder));
}
List<Tuple> CustomerNames = entityManager.createQuery(tupleQuery).getResultList();
return createEntitiesFromTuples(CustomerNames);
}
private Selection<?> getSelection(Root<Customer> root,
SingularAttribute<Customer, ?> attribute) {
return root.get(attribute).alias(attribute.getName());
}
private List<Customer> createEntitiesFromTuples(List<Tuple> CustomerNames) {
List<Customer> customers = new ArrayList<>();
for (Tuple customer : CustomerNames) {
Customer c = new Customer();
c.setId(customer.get(Customer_.id.getName(), Long.class));
c.setName(customer.get(Customer_.name.getName(), String.class));
c.add(customer);
}
return customers;
}
}
Unfortunately Projections does not work with specifications. JpaSpecificationExecutor return only a List typed with the aggregated root managed by the repository ( List<T> findAll(Specification<T> var1); )
An actual workaround is to use Tuple. Example :
#Override
public <D> D findOne(Projections<DOMAIN> projections, Specification<DOMAIN> specification, SingleTupleMapper<D> tupleMapper) {
Tuple tuple = this.getTupleQuery(projections, specification).getSingleResult();
return tupleMapper.map(tuple);
}
#Override
public <D extends Dto<ID>> List<D> findAll(Projections<DOMAIN> projections, Specification<DOMAIN> specification, TupleMapper<D> tupleMapper) {
List<Tuple> tupleList = this.getTupleQuery(projections, specification).getResultList();
return tupleMapper.map(tupleList);
}
private TypedQuery<Tuple> getTupleQuery(Projections<DOMAIN> projections, Specification<DOMAIN> specification) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<DOMAIN> root = query.from((Class<DOMAIN>) domainClass);
query.multiselect(projections.project(root));
query.where(specification.toPredicate(root, query, cb));
return entityManager.createQuery(query);
}
where Projections is a functional interface for root projection.
#FunctionalInterface
public interface Projections<D> {
List<Selection<?>> project(Root<D> root);
}
SingleTupleMapper and TupleMapper are used to map the TupleQuery result to the Object you want to return.
#FunctionalInterface
public interface SingleTupleMapper<D> {
D map(Tuple tuple);
}
#FunctionalInterface
public interface TupleMapper<D> {
List<D> map(List<Tuple> tuples);
}
Example of use :
Projections<User> userProjections = (root) -> Arrays.asList(
root.get(User_.uid).alias(User_.uid.getName()),
root.get(User_.active).alias(User_.active.getName()),
root.get(User_.userProvider).alias(User_.userProvider.getName()),
root.join(User_.profile).get(Profile_.firstName).alias(Profile_.firstName.getName()),
root.join(User_.profile).get(Profile_.lastName).alias(Profile_.lastName.getName()),
root.join(User_.profile).get(Profile_.picture).alias(Profile_.picture.getName()),
root.join(User_.profile).get(Profile_.gender).alias(Profile_.gender.getName())
);
Specification<User> userSpecification = UserSpecifications.withUid(userUid);
SingleTupleMapper<BasicUserDto> singleMapper = tuple -> {
BasicUserDto basicUserDto = new BasicUserDto();
basicUserDto.setUid(tuple.get(User_.uid.getName(), String.class));
basicUserDto.setActive(tuple.get(User_.active.getName(), Boolean.class));
basicUserDto.setUserProvider(tuple.get(User_.userProvider.getName(), UserProvider.class));
basicUserDto.setFirstName(tuple.get(Profile_.firstName.getName(), String.class));
basicUserDto.setLastName(tuple.get(Profile_.lastName.getName(), String.class));
basicUserDto.setPicture(tuple.get(Profile_.picture.getName(), String.class));
basicUserDto.setGender(tuple.get(Profile_.gender.getName(), Gender.class));
return basicUserDto;
};
BasicUserDto basicUser = findOne(userProjections, userSpecification, singleMapper);
I hope it helps.

Resources