Skip to main content

Query

Feature Description

Without Jimmer's support, implementing GraphQL queries requires two types of work:

  • Query aggregate roots

    This is work that needs to be done when developing any data service, whether it's a GraphQL service, REST service, or even a service using other custom protocols.

    Developers only need to query and return standalone aggregate root objects without considering their association properties. So this is a relatively simple development task.

    For Spring GraphQL, just add the @QueryMapping annotation to the query methods in controller classes.

  • Query association properties and calculated properties

    This is the bulk of the work required to implement GraphQL queries, requiring consideration of all current object associations and batch loading. There is some workload involved.

    In other words, it is precisely because the server undertakes these responsibilities that using GraphQL feels free, convenient and powerful on the client side.

    For Spring GraphQL, some schemes are provided for querying association properties, such as

When using Jimmer to implement GraphQL queries, developers only need to focus on querying aggregate roots, just ensuring that these aggregate roots have simple scalar fields. This is because

tip

All association properties and calculated properties of entity objects are automatically mapped to GraphQL fields by Jimmer without any development.

caution

So far, the GraphQL protocol does not support recursive queries for self-associated properties.

Therefore, it is not possible to expose functionality like recursive queries in object fetchers through GraphQL. This is a functionality sacrifice that must currently be accepted when using GraphQL.

Implement GraphQL Service

1. Simplest Implementation

BookStoreService.java
package com.example.business;

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

...Omit other imports...

@Controller
public class BookStoreService {

private final JSqlClient sqlClient;

public BookStoreService(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}

@QueryMapping
public List<BookStore> bookStores(
@Argument @Nullable String name
) {
BookStoreTable table = Tables.BOOK_STORE_TABLE;
return sqlClient
.createQuery(table)
.where(table.name().ilikeIf(name))
.select(table)
.execute();
}
}

Unlike previous REST services, this example does not use object fetchers, returning the simplest orphan objects (orphan objects contain neither associated properties nor calculated properties).

Although we return orphan objects here, if the client's GraphQL request contains associated objects, Jimmer will still automatically load the required associated properties.

However, there is a problem with this approach: in non-cache mode, unnecessary properties will also be queried, which has performance issues. We will introduce another approach with optimization capability later.

2. Optimize Performance

To avoid querying unnecessary non-associated properties in non-cache mode, Jimmer can create a Fetcher object based on graphql.schema.DataFetchingEnvironment and use this Fetcher to query the database with the object as a parameter.

tip

In non-cache mode, not querying unnecessary non-associated properties is the essential difference between Jimmer GraphQL and other GraphQL implementations in the Java ecosystem.

BookStoreService.java
package com.example.business;

import org.springframework.graphql.data.method.annotation.Argument;
import graphql.schema.DataFetchingEnvironment;
import org.babyfish.jimmer.spring.graphql.DataFetchingEnvironments;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

...Omit other imports...

@Controller
public class BookStoreService {

private final JSqlClient sqlClient;

public BookStoreService(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}

@QueryMapping
public List<BookStore> bookStores(
@Argument @Nullable String name,
DataFetchingEnvironment env
) {
BookStoreTable table = Tables.BOOK_STORE_TABLE;
return sqlClient
.createQuery(table)
.where(table.name().ilikeIf(name))
.select(
table.fetch(
DataFetchingEnvironments.createQuery(
BookStore.class,
env
)
)
)
.execute();
}
}

Running Effect

In the accompanying example, the GraphQL related projects are:

LanguageExample
Javajimmer-examples/java/jimmer-sql-graphql
Kotlinjimmer-examples/kotlin/jimmer-sql-graphql-kt

Select any project, open and start it. Finally, use the browser to visit http://localhost:8080/graphiql

note

Be careful, it's /graphiql, not /graphql

Execute the request:

query {
bookStores {
id
name
avgPrice ❶
books { ❷
id
name
edition
price
authors { ❸
id
firstName
fullName
gender
}
}
}
}
  • ❶ Query calculated property BookStore.avgPrice

  • ❷ Query association property BookStore.books

  • ❸ Query association property Book.authors

The return result is:

{
"data":{
"bookStores":[
{
"id":2,
"name":"MANNING",
"avgPrice":80.333333,
"books":[
{
"id":10,
"name":"GraphQL in Action",
"edition":1,
"price":80,
"authors":[
{
"id":5,
"firstName":"Samer",
"fullName":"Samer Buna",
"gender":"MALE"
}
]
},
{
"id":11,
...omitted...
},
{
"id":12,
...omitted...
}
]
},
{
"id":1,
"name":"O'REILLY",
"avgPrice":58.5,
"books":[
{
"id":4,
"name":"Effective TypeScript",
"edition":1,
"price":73,
"authors":[
{
"id":3,
"firstName":"Dan",
"fullName":"Dan Vanderkam",
"gender":"MALE"
}
]
},
{
"id":5,
...omitted...
},
{
"id":6,
...omitted...
},
{
"id":1,
"name":"Learning GraphQL",
"edition":1,
"price":50,
"authors":[
{
"id":2,
"firstName":"Alex",
"fullName":"Alex Banks",
"gender":"MALE"
},
{
"id":1,
"firstName":"Eve",
"fullName":"Eve Procello",
"gender":"FEMALE"
}
]
},
{
"id":2,
"name":"Learning GraphQL",
"edition":2,
"price":55,
"authors":[
{
"id":2,
"firstName":"Alex",
"fullName":"Alex Banks",
"gender":"MALE"
},
{
"id":1,
"firstName":"Eve",
"fullName":"Eve Procello",
"gender":"FEMALE"
}
]
},
{
"id":3,
"name":"Learning GraphQL",
"edition":3,
"price":51,
"authors":[
{
"id":2,
"firstName":"Alex",
"fullName":"Alex Banks",
"gender":"MALE"
},
{
"id":1,
"firstName":"Eve",
"fullName":"Eve Procello",
"gender":"FEMALE"
}
]
},
{
"id":7,
"name":"Programming TypeScript",
"edition":1,
"price":47.5,
"authors":[
{
"id":4,
"firstName":"Boris",
"fullName":"Boris Cherny",
"gender":"MALE"
}
]
},
{
"id":8,
"name":"Programming TypeScript",
"edition":2,
"price":45,
"authors":[
{
"id":4,
"firstName":"Boris",
"fullName":"Boris Cherny",
"gender":"MALE"
}
]
},
{
"id":9,
"name":"Programming TypeScript",
"edition":3,
"price":48,
"authors":[
{
"id":4,
"firstName":"Boris",
"fullName":"Boris Cherny",
"gender":"MALE"
}
]
}
]
}
]
}
}