跳到主要内容

基本用法

引入对象抓取器的原因

提示

一句话查询任意形状的数据结构,就如同GraphQL所做的那样

在了解对象抓取器之前,有必要先了解常规的属性查询。

例如,我需要这样一个SQL:

select 
b.id,
b.name,
b.edition
/* highlight-next-line */
/* 不需要`b.price`和`b.store_id` */
from book b
where b.edition = 3;

在这个SQL中,我们只查询部分列,对应的Jimmer写法:

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

打印输出为 (为了方便阅读,这里认为进行了格式化)

[
{
"_1" : 3,
"_2" : "Learning GraphQL",
"_3" : 3
}
...省略其他对象
]
信息

由于只查询对象的部分属性,所以无法简单地使用select(table),而是用了select(expr1, expr2, ..., expr3)

然而,这会导致返回的结果类型是元组,而非Book对象。

元组中属性的顺序对应select()中的顺序,在上述例子中,每一个元组中的对应关系为:

_1 -> id
_2 -> name
_3 -> edition

如果我们需要返回对象,有一个选择是定义一个BookDTO类型 (假设Java版使用了lombok)

和原始的实体类型Book不同,BookDTO仅仅包含我们需要的属性,而非所有属性。

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

再把元组转化为BookDTO对象,有两种办法

  • 不修改之前的代码,先查询出Tuple3<Long, String, Integer>集合,再用Stream API转换

  • 利用Jimmer的快捷API,直接转换查询结果

这里,我们采用第二种方式,代码如下

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

转化后的结果才是我们需要的类型:

[
{
"id" : 3,
"name" : "Learning GraphQL",
"edition" : 3
}
...省略其他对象
]

JPA和MyBatis就是采用这样的选择:

  • JPA默认返回对象的所有属性,想要自定义返回属性的话,

    • 要么使用HQL: select new BookDTO(...) from ...

    • 或者使用query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(DTO.class))

  • MyBatis需要编写和DTO对应的ResultMapper.

然而,不论是JPA还是MyBatis都避免不了需要额外再写一个DTO类并自己肩负起从查询结构到DTO对象的转化逻辑。

信息

不幸的是

  • 查询业务往往具有多样性,格式多样化必然DTO类型变多*(作为聚合根类型而言可能存在格式多样化问题,作为其他DTO的关联类型会更容易导致格式多样化问题)*。 最终导致

  • 对于JPA而言,这样的做法违背了了ORM的初衷:便捷性

接下来,我们使用Jimmer的对象抓取器来简化这一流程。

使用对象抓取器

Jimmer会根据每个实体接口,生成对应的Fetcher类,这些Fetcher就是对象抓取器。

对象抓取器会根据抓取的属性,自动封装成对象,免去了手动映射以及编写额外DTO类的烦恼。

备注

Annotation processor会为每一个实体接口自动生成一个Fetcher类,在这个例子中,就是BookFetcher

  • 例1:

    上方示例中的查询,以对象抓取器的形式可以写成:

    BookTable table = Tables.BOOK_TABLE;
    List<Book> books = sqlClient.createQuery(table)
    .where(table.edition().eq(3))
    .select(
    table.fetch(
    Fetchers.BOOK_FETCHER
    // id是隐含的、强制的
    .name()
    .edition()
    )
    )
    .execute();
    System.out.println(books);

    可以看到得到的返回值就是Book类型,不需要再进行映射,这为日常编码节省了很多时间。

    输出结果如下:

    [
    {
    "id" : 3,
    "name" : "Learning GraphQL",
    "edition" : 3
    },
    ...省略其他对象...
    ]
  • 例2

    我们可以修改一下代码,不抓取书籍版本,而抓取书籍价格:

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

    此时输出的JSON为:

    [
    {
    "id" : 3,
    "name" : "Learning GraphQL",
    "price" : 51.00
    },
    ...省略其他对象...
    ]
  • 例3

    你甚至可以查询关联对象

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

    此时输出的JSON为:

    [
    {
    "id" : 3,
    "name" : "Learning GraphQL",
    "authors": [
    { "id": 1, "Eve" },
    { "id": 2, "Alex" },
    ]
    },
    ...省略其他对象...
    ]
提示

借助Jimmer实体的动态性,不再需要编写额外的DTO,即可实现多样化的查询业务,只需要改变对象抓取器的形状即可。

对查询返回的输出数据而言,消灭

能极大地提高服务端的生产效率。

和JPA EntityGraph的差别

当对象抓取器用于抓取关联属性时,和JPA的EntityGraph比较类似,但存在如下差异

  • 对象抓取器使用batch-load策略加载关联对象,而非使用JPA中join-fetch的概念,这很重要,因为

  • JPA的EntityGraph仅仅控制关联属性,但对象抓取器可以控制任何属性,和GraphQL一样,精确控制到每个属性

  • 对于因为自关联属性导致的无限递归结构,对象抓取器支持递归查询