I am looking to pass a field value to a resolved field using another object type.
Another way to put it if I have `Customer > User > Profile' - how can I pass the CustomerID field value that would be in customer to Profile as an argument or variable in order to resolve correctly?
There's exactly 5 possibilities (as of graphql-java v12) to provide info to a resolver (DataFetcher) at any level:
1) Directly pass them in the query (possibly on multiple levels):
{customer(id: 3) {
user {
profile(id: 3) {
name
}
}
}
}
2) Get values from the source object
The source is the result of the enclosing query.
In your case, the source for the customer query is the root (whatever you provided at the query execution time, e.g.
graphQL.execute(ExecutionInput.newExecutionInput()
.query(query)
.root(root)
.build())
The source for the user query is whatever customer query returned, presumably some Customer instance.
The source for the profile query is whatever the user query returned, presumably a User instance.
You can get a hold of the source via DataFetchingEnvironment#getSource(). So, if User contains the CustomerID you're after, just get it via ((User) env.getSource()).getCustomerId(). If not, consider wrapping the result into an object that would contain all you need in the sub-queries.
3) Pass the values around using the shared context
graphql-java passes around an instance of GraphQLContext available to all resolvers. So, inside the DataFetcher for customer, you can store the CustomerID into it:
Customer customer = getCustomer();
GraphQLContext context = env.getContext();
context.put("CustomerID", customer.getId());
Later on, inside the DataFetcher for profile, you can get it from the context:
String customerId = env.getContext().get("CustomerID");
To initialize a context, pass it when executing the query:
ExecutionInput input = ExecutionInput.newExecutionInput()
.query(operation)
.graphQLContext(new HashMap<>())
.build()
graphQL.execute(query, input);
This way is stateful, thus the hardest to manage, so use it only if all else fails.
4) Directly get the arguments passed to a parent field
ExecutionStepInfo stepInfo = dataFetchingEnvironment.getExecutionStepInfo();
stepInfo.getParent().getArguments(); // get the parent arguments
5) Pass the values around using the local context
Instead of returning the result directly, wrap it into a DataFetcherResult. That way you can also attach any object as a localContext that will be available to all child DataFetchers via DataFetchingEnvironment#getLocalContext()
Related
I'm working on a project in which I'm using MongoDB with the Spring boot framework, and I'm trying to make a single API endpoint to create and update the Entity/Documents (if id is provided do update, else insert), the endpoint is working as expected but the problem I'm facing is when updating, i have to send all the fields in the payload otherwise save method changes the fields which are not provided to null, I understand that this is caused when we are not providing fields in the payload,
Entity class sets the value of the missing fields to null by default (unless a default value is not provided),
i saw in the internet that we can use mongoTemplate and provide Update definition as follows,
Document document = new Document();
mongoTemplate.getConverter().write(myEntity, document);
Update update = new Update();
update.set("field", "value");
Query query = new Query(Criteria.where("fieldTwo").is(myEntity.getFieldTwo()));
UpdateResult id = mongoTemplate.upsert(query, update, MyEntity.class);
but here, since the fields to be updated are fully dynamic, i cannot set the update definition.
so what i have done so far is, if id is provided, query for the existing record, and loop though the payload, and check whether any fields have null values if there are copy the corresponding value from the existing record as follows
if (newEntity.get_id() != null) {
// update
MyEntity curEntity = myEntityService.findById(newEntity.get_id());
Field[] fields = newEntity.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // ===> here
Object o = field.get(newEntity);
if (o == null) {
field.set(newEntity, field.get(curEntity));
}
}
}
and now it works and fulfills what I was trying to achieve, my first question is, is there any other proper way to achieve this? I have worked on 50+ MERN and MEAN projects but in nodejs world with mongoose, these kinds of checks/implementations are unnecessary.
And my second question is, all the fields in the entity class are private, so I had to add field.setAccessible(true); in order to read the values, is it a safe thing to do?
I'm making a spring boot application, and I'm looking to update an existing entry in the DB through my service and controller. In my service layer I have the below method. So I'm retrieving the fields associated with a caseID, creating a model mapper which maps my entity object class to my VO, and then mapping the retrieved data to my DTO. Then I save my repository. The purpose is to add only the fields which I have specified in my req message ie if I only want to update 1 field out of 20, it updates this field and leaves the rest untouched. The below runs successfully, but the field I specify in my request message in postman does not update in the DB. Why is this? I have tried mapping different objects and saving different variables to the repository but nothing seems to update the DB.
public StoredOutboundErrorCaseVO updateCase(OutboundErrorCaseVO outboundErrorCaseVO, Long caseNumber) {
OutboundErrorCaseData existingCaseData = ErrorCaseDataRepository.findById(caseNumber).get();
ModelMapper mm = new ModelMapper();
mm.getConfiguration().setAmbiguityIgnored(true);
OutboundErrorCaseData uiOutboundErrorCaseData = mm.map(outboundErrorCaseVO,
OutboundErrorCaseData.class);
mm.map(existingCaseData, uiOutboundErrorCaseData);
ErrorCaseDataRepository.save(uiOutboundErrorCaseData);
return mm.map(uiOutboundErrorCaseData, StoredOutboundErrorCaseVO.class);
}
Controller - code omitted for brevity, POST method (I usually use PUT for updates but I believe I can still use POST)
StoredOutboundErrorCaseVO updatedCase = outboundErrorService.updateCase(outboundErrorCaseVO,
caseNumber);
Repo
#Repository
public interface OutboundErrorCaseDataRepository extends JpaRepository<OutboundErrorCaseData, Long> {
You are getting data and passing it into existingCaseData and save uiOutboundErrorCaseData. So my guess is Hibernate is adding a new object into the database with new Id and with you updated value. It of course depends on your model definition. Especially id.
I also think Hibernate won't let you save uiOutboundErrorCaseData with the same Id if you already have an object in Hibernate Session associated with that id. So, why don't you update existingCaseData with the new value and save it back.
I created a working solution, although I realise it can be improved, it certainly works. The only drawback is that I need to specify all the fields which can be updated, ideally I want a solution which takes in n number of fields and updates the record.
OutboundErrorCaseData existingCaseDta = ErrorCaseDataRepository.findById(caseNumber).get();
if (outboundErrorCaseVO.getChannel() != null) {
existingCaseDta.setChannel(outboundErrorCaseVO.getChannel());
}
ErrorCaseDataRepository.save(existingCaseDta);
ModelMapper mm = new ModelMapper();
return mm.map(existingCaseDta, StoredOutboundErrorCaseVO.class);
How can I access this value, the max value and message from the controller
I try to access the information schema table, do it via sql and access this variable without writing it directly in the code,
I was trying to access the schema Information table, to get the maximum value from the column but it generates an error that is not mapped
thank you very much and sorry, I'm new to this
There are 2 options to do this.
Since the value is constant, you can declare a static final variable and use it as the class variable.
eg:
static final int MAX_RANGE = 100;
In this case, just use "class_name".MAX_RANGE
You could also specify the value in a property file and use the property file as and when needed.
You can use reflection (which is not the best case)
Class<?> cls = Class.forName(name);
cls.getDeclaredAnnotations(); // get all annotation
(cls.getDeclaredMethods()[0]).getAnnotations(); //get annotation of a method
Annotation ety = cls.getAnnotation(Annotation.class); // get annotation of particular annotation class
I have the following simple code:
#Test
public void saveExpense() {
// Create dummy Expense object i.e. { "description": "Short Description", "date": etc }
Expense expenseToSave = ExpenseHelper.createExpense("Short Description", new Date(), user);
Expense savedExpense = expenseService.save(expenseToSave);
// What is strange, is that here, both expenseToSave and savedExpense have id set to 1 for example; after save the expense should have an id;
Expense expected = ExpenseHelper.createExpense("Short Description", new Date(), user);
// Check if expected object is equal to the saved one
Assert.assertTrue(expected.equals(expenseService.findByDescription("Short Description")));
}
Normally I would expect that expenseToSave to be without id and savedExpense with id, but both have id after save. Why?
That made another variable to be necessary and complicate the test.
Thanks.
That's just how the Hibernate Session.save() method is specified. From the documentation:
Persist the given transient instance, first assigning a generated
identifier. (Or using the current value of the identifier property if
the assigned generator is used.) This operation cascades to associated
instances if the association is mapped with cascade="save-update".
IDs are the mechanism how Hibernate differentiates between persisted and transient objects, and how it identifies specific objects. Therefore, the ID is set early in the persistence step, as for example cyclic references in an object tree are resolved via IDs while persisting.
What differentiates the returned object vs. the original object is that the returned object is attached to the Hibernate session. For example, with active cascading, contained entities (e.g. in a one-to-many collection) are now persistent instances as well in the returned object.
Please be aware that
void EntityManager#persist(java.lang.Object entity)
(http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#persist%28java.lang.Object%29)
Persists the given object by changing the object passed in and does not return a persisted copy - I suspect your ExpenseHelper to return the original object additionally so that you receive the same object via return as you already have by passing it in.
This follows a common anti-pattern for a kind of unified behaviour of DAO to be something like
public T create(T entity) {
this.entityManager.persist(entity);
return entity;
}
to get a kind of synchronicity with saving something
public T save(T entity) {
return this.entityManager.merge(entity);
}
Where
<T> T EntityManager#merge(T entity)
does indeed merge and pass you the merged entity.
It can depend on Hibernate mapping of the Expense entity, or implementation of ExpenseHelper class.
Also, take a look on Expense.equals() implementation.
Based on this statement:
Expense savedExpense = expenseService.save(expenseToSave);
the value of the savedExpense object will depend on what your are doing in the save method. Usually save methods don't return an object. You already have a reference to the object that you just saved (expenseToSave) available to you. And you are trying to assert that your expected object equals the object that was saved, which is fine. So I am not sure what the purpose of returning an object in expenseService.save(expenseToSave)
Also, note that the id of the object expenseToSave would have been populated by your ORM (Hibernate, I assume) based on your configuration, when you save it. There is no need to return this object or another object in the save method.
I have written a test, which I know is wrong. I know that this gets the same instance for originalProduct and updatedProduct so that when I call updatedProduct.setProductName("Updated Product Name"); it updates the productName member of both originalProduct and updatedProduct. How can I change this so that I get 2 different instances of this object.
#Test
#Transactional
public void testUpdateProduct() {
productDao.addProduct(createTempProduct());
Product originalProduct = productDao.getProduct((long)999);
Product updatedProduct = productDao.getProduct((long)999);
updatedProduct.setProductName("Updated Product Name");
productDao.updateProduct(updatedProduct);
Product newProduct = productDao.getProduct((long)999);
Assert.assertNotSame(originalProduct, newProduct);
Assert.assertSame(updatedProduct, newProduct);
}
You're hitting Hibernate's first-level cache. In other words, every call to productDao.getProduct(999) within the scope of that test will return the same Product instance because the first time you load it, the instance is stored in the Session just in case you ask for it again. In order to avoid this, you can either evict the specific object from the Session or clear all objects from the Session between the calls.
Create a new Product, and get all the values from the DB-product and set those values in the new object. Then its a dupe of the original object, at least in terms of values.