Basic Usage
Why Introduce Object Fetchers
One statement to query data structure of arbitrary shape, like GraphQL
Before understanding object fetchers, it is necessary to first understand conventional property queries.
For example, I need a SQL like this:
select
b.id,
b.name,
b.edition
/* highlight-next-line */
/* Don't need `b.price` and `b.store_id` */
from book b
where b.edition = 3;
In this SQL, we only query a part of columns. The corresponding Jimmer code is:
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<Tuple3<Long, String, Integer>> tuples = sqlClient
.createQuery(book)
.where(table.edition().eq(3))
.select(
table.id(),
table.name(),
table.edition()
)
.execute();
System.out.println(tuples);
val tuples = sqlClient
.createQuery(Book::class) {
where(table.edition.eq(3))
select(
table.id
table.name
table.edition
)
}
.execute()
println(tuples)
The printed output is (formatted for readability):
[
{
"_1" : 3,
"_2" : "Learning GraphQL",
"_3" : 3
}
...Other objects omitted
]
Since a part of object properties are queried, select(table)
cannot be simply used. Instead, select(expr1, expr2, ..., expr3)
is required.
However, this results in tuples being returned instead of objects.
In the tuples, the property order corresponds to the select()
order. In the above example, the mapping in each tuple is:
_1 -> id
_2 -> name
_3 -> edition
If we want to return objects, one choice is to define a BookDTO type (assuming lombok is used for Java):
Unlike the original Book
entity type, BookDTO
only contains the properties we need rather than all properties.
- Java
- Kotlin
@Data
public class BookDTO {
private long id;
private String name;
private int edition;
}
data class BookDTO(
val id: Long,
val name: String,
val edition: Int
)
There are two ways to convert the tuples to BookDTO
objects:
-
Keep previous code unchanged, first query
Tuple3<Long, String, Integer>
collection, then convert using Stream API -
Use Jimmer's utility API to directly transform the query result
Here we use the second approach:
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<BookDTO> bookDTOs = sqlClient
.createQuery(book)
.where(table.edition().eq(3))
.select(
table.id(),
table.name(),
table.edition()
)
.map(tuple -> {
BookDTO dto = new BookDTO();
dto.setId(tuple.get_1());
dto.setName(tuple.get_2());
dto.setEdition(tuple.get_3());
return dto;
});
System.out.println(bookDTOs);
val bookDTOs = sqlClient
.createQuery(Book::class) {
where(table.edition.eq(3))
select(
table.id
table.name
table.edition
)
}
.map {
BookDTO(it._1, it._2, it._3)
}
println(tuples)
The result after conversion is the type we want:
[
{
"id" : 3,
"name" : "Learning GraphQL",
"edition" : 3
}
...Other objects omitted
]
JPA and MyBatis make this kind of choice:
-
JPA returns all scalar properties of an object by default. If you want to customize the returned properties,
-
Either use HQL:
select new BookDTO(...) from ...
-
Or use
query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(DTO.class))
-
-
MyBatis needs to write a
ResultMapper
corresponding to the DTO.
However, neither JPA nor MyBatis can avoid having to write an additional DTO class and take on the logic of transforming from the query structure to the DTO object themselves.
Unfortunately
-
Query business often has diversity, and inevitable format diversity means more DTO types (as aggregate root types there may be format diversity issues, and as associated types of other DTOs it will be easier to lead to format diversity issues). This eventually leads to
-
For JPA, this goes against the original intention of ORM: convenience
Next, we'll use Jimmer's object fetcher to simplify this process.
Using Object Fetchers
For each entity interface, Jimmer generates a corresponding Fetcher class. These Fetchers are object fetchers.
Based on the fetched properties, object fetchers automatically package objects, eliminating the need to manually map and write additional DTO classes.
The Annotation processor will automatically generate a Fetcher class for each entity interface, which is BookFetcher
in this example.
-
Example 1:
The query in the above example can be written as an object fetcher:
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<Book> books = sqlClient.createQuery(table)
.where(table.edition().eq(3))
.select(
table.fetch(
Fetchers.BOOK_FETCHER
// id is implicit, mandatory
.name()
.edition()
)
)
.execute();
System.out.println(books);val books = sqlClient
.createQuery(Book::class) {
where(table.edition.eq(3))
select(
table.fetchBy {
// id is implicit, mandatory
name
edition
}
)
}
.execute()
println(books)As you can see, the return value is of type
Book
, no need for further mapping, which saves a lot of time for daily coding.The output is as follows:
[
{
"id" : 3,
"name" : "Learning GraphQL",
"edition" : 3
},
...Other objects omitted...
] -
Example 2
We can modify the code a little, not fetching the book edition, but fetching the book price:
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<Book> books = sqlClient.createQuery(table)
.where(table.edition().eq(3))
.select(
table.fetch(
Fetchers.BOOK_FETCHER
.name()
.price()
)
)
.execute();
System.out.println(books);val books = sqlClient
.createQuery(Book::class) {
where(table.edition.eq(3))
select(
table.fetchBy {
name
price
}
)
}
.execute()
println(books)The JSON output now is:
[
{
"id" : 3,
"name" : "Learning GraphQL",
"price" : 51.00
},
...Other objects omitted...
] -
Example 3
You can even query associated objects
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<Book> books = sqlClient.createQuery(table)
.where(table.edition().eq(3))
.select(
table.fetch(
Fetchers.BOOK_FETCHER
.name()
.authors(
Fetchers.AUTHOR_FETCHER
.firstName()
)
)
)
.execute();
System.out.println(books);val books = sqlClient
.createQuery(Book::class) {
where(table.edition.eq(3))
select(
table.fetchBy {
name
authors {
firstName
}
}
)
}
.execute();
println(books)The JSON output now is:
[
{
"id" : 3,
"name" : "Learning GraphQL",
"authors": [
{ "id": 1, "Eve" },
{ "id": 2, "Alex" },
]
},
...Other objects omitted...
]
With the dynamicity of Jimmer entities, there is no need to write additional DTOs to achieve diversified query business, just change the shape of the object fetcher.
For the output data of the query, eliminating
can greatly improve server-side productivity.Differences from JPA EntityGraph
When object fetchers are used to fetch association properties, they are similar to JPA's EntityGraph, but have the following differences:
-
Object fetchers use
batch-load
strategy to load associated objects, not JPA'sjoin-fetch
concept. This is important because:-
Fetching collection associations does not break paginated queries like JPA's EntityGraph.
-
More friendly to association cache and remote associations.
-
-
JPA's EntityGraph only controls association properties, but object fetchers can control any properties, same as GraphQL, with precision down to each property.
-
For infinite recursive structures caused by self-associated properties, object fetchers support recursive query.