Graphql java nested query resolver - java

I have requirements graph nested query with java resolver.
getAccounts(type: "01",transactionMonths: 12){
accountNumber,
openDate,
productType,
accountTransactions(annualFee: True){
amount,
date
}
}
How can we write query in graphql and how to write java resolver for nested query.
How to fetch the nested query arguments to to pass to my jparepository.
i have Account type and Transactions type as below
type Account{
accountNumber: String
openDate: String
type: String
transactionMonths: String
productType: String
accountTransactions:[AccountTransaction]
}
type AccountTransaction{
amount: String
date:String
annualFee:Boolean
}
How can i retrive the accountTransactions in accounts using nested query using java resolver.

Did you look into implementing a GraphQLResolver as explained for the BookResolver in this link ?
If you read the above link, you should be able to write something like this:
public class AccountResolver implements GraphQLResolver<Account> {
public Collection<AccountTransaction> accountTransactions(Account account,Boolean annualFee) {
// put your business logic here that will call your jparepository
// for a given account
}
}
As for your java DTO, you should have something like this:
public class Account {
private String accountNumber;
private String openDate;
private String type;
private String transactionMonths;
private String productType;
// Don't specify a field for your list of transactions here, it should
// resolved by our AccountResolver
public Account(String accountNumber, String openDate, String type, String transactionMonths, String productType) {
this.accountNumber = accountNumber;
this.openDate = openDate;
this.type = type;
this.transactionMonths = transactionMonths;
this.productType = productType;
}
public String getAccountNumber() {
return accountNumber;
}
public String getOpenDate() {
return openDate;
}
public String getType() {
return type;
}
public String getTransactionMonths() {
return transactionMonths;
}
public String getProductType() {
return productType;
}
}
Let me explain a bit more the code concerning the resolver above:
You create a new resolver for your Account DTO;
It has one method with the exact same signature as your GraphQL field in Account, that is: accountTransactions, returning a collection of AccountTransaction.
As for the Java DTO:
It specify all the fields of your GraphQL type but NOT the field you want your resolver to resolve.
SpringBoot GraphQL will autowire the resolver, and it will be called if the client asked for details on the account and its transactions.
Assuming that you have defined a GraphQL query called accounts that returns all accounts, the following GraphQL query would be valid:
{
accounts {
accountNumber
accountTransactions {
amount
date
annualFee
}
}
}

Related

How to pass not null values #RequestParameter in controller?

I am trying to update an Entity by using spring boot 2.5.3 in the controller method.
http://localhost:5000/api/v1/student/1
with the following payload.
{
"name":"abc",
"email":"abc#email.com",
"dob":"2000-06-14"
}
These values are not updated. They are getting null values when I inspected them using a debugger.
Here is my controller method.
#PutMapping(path = "/{id}")
public ResponseEntity<?> updateStudent(#PathVariable("id") Long id, #RequestParam(required = false) String name, #RequestParam(required = false) String email) {
Student savedStudent = studentService.updateStudent(id, name, email);
return ResponseEntity.ok(savedStudent);
}
Email and name are optional.
In debugger: name:null,email:null. Why are they getting null values?
What is the correct way to pass values from the controller?
#Transactional
// We are not using any query from the repository because we have the service method with transactional annotation.
public Student updateStudent(Long studentId, String name, String email) {
Student student = studentRepository.findById(studentId).orElseThrow(()->new EntityNotFoundException("Student with id " + studentId + " does not exists."));
if (name!= null && name.length()>0 && !Objects.equals(name,student.getName())){
student.setName(name);
}
if (email!= null && email.length()>0 && !Objects.equals(email,student.getEmail())){
Optional<Student> optionalStudent = studentRepository.findStudentByEmail(email);
if (optionalStudent.isPresent()){
throw new IllegalStateException("Email is already taken");
}
student.setEmail(email);
}
System.out.println(student);
Student savedStudent= studentRepository.save(student);
return savedStudent;
}
{
"name":"abc",
"email":"abc#email.com",
"dob":"2000-06-14"
}
This is not a request parameter but the request body. You need to create a class and use #RequestBody annotation.
#Data
public class UpdateStudentRequest {
private String id;
private String name;
private String email;
}
#PutMapping(path = "/{id}")
public ResponseEntity<?> updateStudent(#PathVariable("id") Long id, #RequestBody UpdateStudentRequest request) {
Student savedStudent = studentService.updateStudent(
request.getId(), request.getName(), request.getEmail());
return ResponseEntity.ok(savedStudent);
}
If you want to send the request parameters as... URL parameters:
http://localhost:5000/api/v1/student/1?name=abc&email=abc#email.com
You aren't sending it as a param (after ?).
http://localhost:5000/api/v1/student/1?name=John Could do the trick.
Since you are POSTing an HTTP request with a content body (being in JSON in your case), you need to map the body using the #RequestBody annotation:
#PutMapping(path = "/{id}")
public ResponseEntity<?> updateStudent(#PathVariable("id") Long id, #RequestBody StudentDTO student) {
Student savedStudent = studentService.updateStudent(
id, student.getName(), student.getEmail());
return ResponseEntity.ok(savedStudent);
}
The StudentDTO would be a lightweight type reflecting your input payload:
public class StudentDTO {
private String name;
private String email;
private String dob;
// setters and getters
}
Otherwise, to keep your RestController signature and use the #RequestParametrized fields, you should send a request of following shape:
http://localhost:5000/api/v1/student/1?name=abc&email=abc#email.com&dob=2000-06-14

JPA session factory query returning Null list

I am developing a spring boot rest api, in that case when i am using a custom query from repository it is returning a null list, and the worst thing is there in no exception on eclipse console.
**** Another Thing**********
I have another controller having different service and repository, that is working correctly.
Entity
#Entity
#Table(name="Navigation_Master")
public class Navigation_Master {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="Nav_ID")
private int Nav_ID;
#Column(name="Nav_TS_ID")
private int Nav_TS_ID;
#Column(name="Nav_Application_ID")
private int Nav_Application_ID;
#Column(name="Nav_Page_ID")
private int Nav_Page_ID;
#Column(name="Nav_Reg_ID")
private int Nav_Reg_ID;
#Column(name="Nav_UnloadEvent")
private String Nav_UnloadEvent;
#Column(name="Nav_RedirectEvent")
private String Nav_RedirectEvent;
#Column(name="Nav_AppCache")
private String Nav_AppCache;
#Column(name="Nav_TTFB")
private String Nav_TTFB;
#Column(name="Nav_Processing")
private String Nav_Processing;
#Column(name="Nav_DomInteractive")
private String Nav_DomInteractive;
#Column(name="Nav_DomComplete")
private String Nav_DomComplete;
#Column(name="Nav_ContentLoad")
private String Nav_ContentLoad;
#Column(name="Nav_PageLoad")
private String Nav_PageLoad;
#Column(name="Nav_EntrySyetemTimes")
private Timestamp Nav_EntrySyetemTimes;
// Getter & setter $ constructors
}
Controller:
#GetMapping("/getNavs/{tcId}/{appid}/{pageId}/{uId}")
public List<Navigation_Master> findPageByName(#PathVariable("appid") String appid, #PathVariable("pageId") String pageId, #PathVariable("tcId") String tcId,
#PathVariable("uId") String uId) {
return navigation_MasterService.getTopOneNavigation(tcId, appid, pageId, uId);
}
Service:
#Override
public List<Navigation_Master> getTopOneNavigation(String appid, String pageId, String tcId, String uId) {
System.out.println(appid+" " + pageId + " "+ tcId + uId);
return navigation_MasterReposity.findNavByParam(Integer.parseInt(tcId), Integer.parseInt(appid), Integer.parseInt(pageId), Integer.parseInt(uId));
}
Repository:
public interface Navigation_MasterReposity extends JpaRepository<Navigation_Master, Integer> {
#Query(value="select * from Navigation_Master p where p.Nav_TS_ID=:tcid and p.Nav_Application_ID=:apid and p.Nav_Page_ID=:pgid and p.Nav_Reg_ID=:uid order by p.Nav_ID desc", nativeQuery=true)
List<Navigation_Master> findNavByParam(#Param("tcid") int tcid,
#Param("apid") int apid,
#Param("pgid") int pgid,
#Param("uid") int uid);
}
in case when i am running this i am getting an empty list. please help me solve the issue, Thanks in advance.
Check from the console/logs if the requested query is correct with the right values in the binding parameters.
To see the sql and the binding parameters, add these 2 lines in application.properties/yaml:
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
Cheers

JsonGetter giving null value

In my Spring Boot application I am creating a REST API, which is calling some other external REST API. I created User class, which is a object that is received by my Rest API downloaded from the external API. My user model looks like:
#JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String fullName;
private String department;
#JsonGetter("fullName")
public String getFullName() {
return fullName;
}
#JsonSetter("full_name")
public void setFullName(String fullName) {
this.fullName = fullName;
}
#JsonGetter("department")
public String getDepartment() {
return department;
}
#JsonSetter("department")
public void setDepartment(String department) {
this.department = department;
}
}
I am using JsonGetter and JsonSetter properties, because I would like to have my json properties in response returned in camelCase, but the properties given in external API are returned with underscore:
External API Response:
{
"full_name": "User A",
"department": "A",
}
My API Response:
{
"fullName": "User A",
"department": "A",
}
And everything seems to be working fine (hitting my API with Postman gives proper responses) until I started to create some Http request tests. In tests I receive assertion error that fullName property is null, while doing the same request in postman is responding with proper responses.
My test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void shouldReturnUserFullName() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/users/a",
User.class)).extracting(User::getFullName)
.contains("User A");
}
}
My controller method:
#GetMapping("users/{name}")
public ResponseEntity<User> getSpecificUserByName(#PathVariable("name") String name) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> entity = new HttpEntity<>(headers);
ResponseEntity<User> response = restTemplate.exchange(createUriString(name), HttpMethod.GET, entity, User.class);
return response;
}
Test result:
java.lang.AssertionError:
Expecting:
<[null]>
to contain:
<["User A"]>
but could not find:
<["User A"]>
I would appreciate any help with this issue :)
#JsonSetter("full_name") expects your API response to contain a property full_name during deserialzation. Since #JsonGetter("fullName") converts full_name to fullName, field private String fullName; is never set.
You should change #JsonSetter("full_name") to #JsonSetter("fullName").
Let us take an example
Suppose your REST API returns below Object of User class
User reponse = new User();
response.setFullName("User A");
response.setDepartment("A");
So, when we call your REST API, the JSON response would look like as below
{
"fullName":"User A",
"department":"A"
}
Now, When you pass this JSON to convert into User class, Jackson will look for methods with the name setFullName and setDepartment.
In your test case, something similar is happening,
for code
this.restTemplate.getForObject("http://localhost:" + port + "/users/a",User.class)
First, it calls your API to get the User object Serialized and then it Deserialized it to User class. While Deserializing, it looks for a method named
setFullName without any
#Setter
#JsonProperty
#JsonAlias
annotations
or will look for any setter method with
#Setter("fullName")
#JsonProperty("fullName"),
#JsonAlias("fullName")
but in your case, the fullName setter is treated as
public void setFull_name(String fullName) {
this.fullName = fullname;
}
So, setter for fullName is not found but since you marked your User class as
#JsonIgnoreProperties(ignoreUnknown = true)
hence any exception is not thrown but fullName for your Response JSON is ignored, so fullName is never set, which remains null and your Test case is failing.
So, either change your test case or mark your setter with
#JsonAlias("fullName")
annotation.
i.e. Your User class will look like as below
#JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String fullName;
private String department;
#JsonGetter("fullName")
public String getFullName() {
return fullName;
}
#JsonAlias({"fullName","full_name"})
public void setFullName(String fullName) {
this.fullName = fullName;
}
#JsonGetter("department")
public String getDepartment() {
return department;
}
#JsonSetter("department")
public void setDepartment(String department) {
this.department = department;
}
}

Spring Data JPA Unable to locate Attribute with the given name

I was trying to use Spring Data JPA on Spring Boot and I kept getting error, I can't figure out what the problem is:
Unable to locate Attribute with the the given name [firstName] on
this ManagedType [com.example.h2demo.domain.Subscriber]
FirstName is declared in my entity class. I have used a service class with DAO before with different project and worked perfectly.
My Entity class (getters and setters are also in the class) :
#Entity
public class Subscriber {
#Id #GeneratedValue
private long id;
private String FirstName,LastName,Email;
public Subscriber(long id, String firstName, String lastName, String email) {
this.id = id;
this.FirstName = firstName;
this.LastName = lastName;
this.Email = email;
}
}
...
My Repository Class
#Component
public interface SubscriberRepository extends JpaRepository<Subscriber,Long> {
Subscriber findByFirstName(String FirstName);
Subscriber deleteAllByFirstName(String FirstName);
}
My Service Class
#Service
public class SubscriberService {
#Autowired
private SubscriberRepository subscriberRepository;
public Subscriber findByFirstName(String name){
return subscriberRepository.findByFirstName(name);
}
public Subscriber deleteAllByFirstName(String name){
return subscriberRepository.deleteAllByFirstName(name);
}
public void addSubscriber(Subscriber student) {
subscriberRepository.save(student);
}
}
And My Controller class:
#RestController
#RequestMapping("/subscribers")
public class SubscriberController {
#Autowired
private SubscriberService subscriberService;
#GetMapping(value = "/{name}")
public Subscriber findByFirstName(#PathVariable("name") String fname){
return subscriberService.findByFirstName(fname);
}
#PostMapping( value = "/add")
public String insertStudent(#RequestBody final Subscriber subscriber){
subscriberService.addSubscriber(subscriber);
return "Done";
}
}
Try changing private String FirstName,LastName,Email; to private String firstName,lastName,email;
It should work.
findByFirstName in SubscriberRepository tries to find a field firstName by convention which is not there.
Further reference on how properties inside the entities are traversed https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-property-expressions
The same problem was when i had deal with Spring Data Specifications (https://www.baeldung.com/rest-api-search-language-spring-data-specifications)
Initial piece of code was:
private Specification<Project> checkCriteriaByProjectNumberLike(projectNumber: String) {
(root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("project_number"), "%" + projectNumber)
}
The problem was in root.get("project_number"). Inside the method, I had to put the field name as in the model (projectNumber), but I sent the field name as in the database (project_number).
That is, the final correct decision was:
private Specification<Project> checkCriteriaByProjectNumberLike(projectNumber: String) {
(root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("projectNumber"), "%" + projectNumber)
}
After I change my entity class variables from capital letter to small letter for instance Username to username the method Users findByUsername(String username); is working for me now .
As per specification , the property names should start with small case.
...The resolution algorithm starts with interpreting the entire part (AddressZipCode) as the property and checks the domain class for a property with that name (uncapitalized)....
It will try to find a property with uncapitalized name. So use firstName instead of FristName and etc..

Mybatis select with nested objects

I am using MyBatis to do a simple select.
Assume we have the following classes:
class Book {
private String bookName;
public Book(String bookName){
this.bookName = bookName;
}
public String getBookName(){
return bookName;
}
}
class Student {
private String studentName;
private Book book;
public Student(){}
// getters and setters
}
I have an annotation on a method that returns a Student object.
#Select("Select studentName, book from Students")
My Issue is that book is always null. I was under the assumption MyBatis will call the constructor with that JDBC type (in this case String) to populate book. What am I missing or doing incorrectly?
One option is
Use #ConstructorArgs annotations to explicitly call Constructor method.
#Select("Select studentName, book from Students")
#ConstructorArgs(value = {
#Arg(column = "studentName", javaType=java.lang.String.class),
#Arg(column = "book", javaType = java.lang.String.class)
})
and pass them to Student constructor, which calls Book constructor.

Categories

Resources