Skip to main content

Basic Usage

Why Introduce Object Fetchers

tip

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:

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);

The printed output is (formatted for readability):

[
{
"_1" : 3,
"_2" : "Learning GraphQL",
"_3" : 3
}
...Other objects omitted
]
info

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.

BookDTO.java
@Data
public class BookDTO {
private long id;
private String name;
private int edtion;
}

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:

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);

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.

info

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.

note

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:

    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);

    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:

    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);

    The JSON output now is:

    [
    {
    "id" : 3,
    "name" : "Learning GraphQL",
    "price" : 51.00
    },
    ...Other objects omitted...
    ]
  • Example 3

    You can even query associated objects

    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);

    The JSON output now is:

    [
    {
    "id" : 3,
    "name" : "Learning GraphQL",
    "authors": [
    { "id": 1, "Eve" },
    { "id": 2, "Alex" },
    ]
    },
    ...Other objects omitted...
    ]
tip

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's join-fetch concept. This is important because:

  • 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.