DTO查询
之前我们介绍了,利用对象抓取器灵活控制被查询数据结构的形状。
现在,我们介绍一个与之等价的功能:查询DTO对象。
Jimmer提供了DTO语言。
该语言本质上是对象抓取器的另外一种表达方式
利用该语言,开发人员可以快速以某个实体类型为聚合根定义多种数据结构的形状,编译器会为每种形状定义生成相应的Java/Kotlin DTO类。每个DTO类型都包含和原动态类型之间的彼此转化逻辑,以及一个和自身形状匹配的对象抓取器。
某些情况下,服务端查询出某种形状的数据后,并不是为了作为HTTP请求的返回,而是自己用,用来驱动后续的复杂的业务逻辑,这是采用这种方式的理想场合。
注意,如果服务端查询某种形状的数据不是为了自己用,而是为了直接作为HTTP请求的返回值,则更推荐直接返回动态实体对象,并利用客户端篇中的方案自动生成开发体验很高的客户端代码。
定义DTO的形状
本文侧重于讲解如何查询静态DTO类型,并 非系统性介绍DTO语言,请参考对象篇/DTO转换/DTO语言以了解完整的DTO语言。
假如Book
类的全名为com.yourcompany.yourproject.model.Book
,你可以
-
在实体定义所在项目中,建立目录
src/main/dto
-
在
src/main/dto
下,按实体类型所处的包路径建立子目录com/yourcompany/yourproject/model
-
在上一步建立的目录下,建立文件
Book.dto
,文件必须和实体类同名,扩展名必须为dto
-
编辑此文件,利用DTO语言,定义Book实体的各种DTO形状
Book.dtoBookDetailView {
#allScalars
store {
#allScalars
}
authors {
#allScalars
}
}
SimpleBookView { ...略... }
...省略其他DTO形状定义...
自动生成DTO类型
Jimmer负责编译dto文件,自动生成符合这些形状的DTO类型。
如果除了dto文件外还有其他Java/Kotlin原代码文件被修改了,直接点击IDE中运行按钮可以导致dto文件的重新编译
但是,如果除了dto文件外没有其他Java/Kotlin文件被修改,简单地点击IDE中运行按钮并不会导致dto文件被重新编译,除非显式地rebuild!
如果你使用的构建工具是Gradle,也可以使用社区提供的第三方Gradle插件来解决这个问题: jimmer-gradle
以上面代码中的BookDetailView
为例,此dto文件被Jimmer成功编译后,会自动生成如下DTO类型
- Java
- Kotlin
package com.yourcompany.yourproject.model.dto;
import com.yourcompany.yourproject.model.Book;
import org.babyfish.jimmer.View;
@GeneratedBy(file = "<your_project>/src/main/dto/Book.dto")
public class BookDetailView implements Input<Book> {
private long id;
private String name;
private int edition;
private BigDecimal price;
private TargetOf_store store;
private List<TargetOf_authors> authors;
public static class TargetOf_store implements Input<BookStore> {
private long id;
private String name;
@Nullable
private String website;
...省略其他成员...
}
public static class TargetOf_authors implements Input<Author> {
private long id;
private String firstName;
private String lastName;
private Gender gender;
...省略其他成员...
}
...省略其他成员...
}
package com.yourcompany.yourproject.model.dto
import com.yourcompany.yourproject.model.Book
import org.babyfish.jimmer.View
@GeneratedBy(file = "<your_project>/src/main/dto/Book.dto")
data class BookDetailView(
val id: Long = 0,
val name: String = "",
val edition: Int = 0,
val price: BigDecimal,
val store: TargetOf_store? = null,
val authors: List<TargetOf_authors> = emptyList(),
) : Input<Book> {
data class TargetOf_store(
val id: Long = 0,
val name: String = "",
val website: String? = null,
) : Input<BookStore> {
...省略其他成员...
}
data class TargetOf_authors(
val id: Long = 0,
public val firstName: String = "",
public val lastName: String = "",
public val gender: Gender,
) : Input<Author> {
...省略其他成员...
}
...省略其他成员...
}
-
生成的DTO类所在的包并非实体所处的包,而是其
dto
子包 -
对于Java而言,假设用户已经使用了lombok
查询DTO对象
使用findById
- Java
- Kotlin
BookDetailView view = sqlClient.findNullable(
BookDetailView.class,
1L
);
System.out.println(view);
val view = sqlClient.findNullable(
BookDetailView::class,
1L
);
println(view)
打印结果如下 (为方便阅读,人为进行了格式化)
BookDetailView(
id=1,
name=Learning GraphQL,
edition=1,
price=50.00,
store=BookDetailView.TargetOf_store(
id=1,
name=O'REILLY,
website=null,
version=0
),
authors=[
BookDetailView.TargetOf_authors(
id=2,
firstName=Alex,
lastName=Banks,
gender=MALE
),
BookDetailView.TargetOf_authors(
id=1,
firstName=Eve,
lastName=Procello,
gender=FEMALE
)
]
)
不难发现,虽然现在查询不再返回动态实体对象,但功能和却和对象抓取器完全一样。这什么为什么呢?
由DTO语言编译而来的DTO类型,都会包含与其形状匹配的对象抓取器,如下
- Java
- Kotlin
@Data
public class BookDetailView implements View<Book> {
public static final ViewMetadata<Book, BookDetailView> METADATA =
new ViewMetadata<Book, BookDetailView>(
Fetchers.BOOK_FETCHER
.name()
.edition()
.price()
.store(TargetOf_store.METADATA.getFetcher())
.authors(TargetOf_authors.METADATA.getFetcher()),
BookDetailView::new
);
@Data
public static class TargetOf_store implements View<BookStore> {
public static final ViewMetadata<BookStore, TargetOf_store> METADATA =
new ViewMetadata<BookStore, TargetOf_store>(
Fetchers.BOOK_STORE_FETCHER
.name()
.website()
.version(),
TargetOf_store::new
);
...省略其他成员...
}
@Data
public static class TargetOf_authors implements View<Author> {
public static final ViewMetadata<Author, TargetOf_authors> METADATA =
new ViewMetadata<Author, TargetOf_authors>(
Fetchers.AUTHOR_FETCHER
.firstName()
.lastName()
.gender(),
TargetOf_authors::new
);
...省略其他成员...
}
...省略其他成员...
}
@Data
data class BookDetailView(
...略...
) : View<Book> {
...省略其他成员...
companion object {
@JvmStatic
public val METADATA: ViewMetadata<Book, BookDetailView> =
ViewMetadata<Book, CompositeBookInput>(
newFetcher(Book::class).by {
name()
edition()
price()
store(TargetOf_store.METADATA.fetcher)
authors(TargetOf_authors.METADATA.fetcher)
}
) {
CompositeBookInput(it)
}
}
@Data
public static class TargetOf_store implements View<BookStore> {
...省略其他成员...
companion object {
@JvmStatic
val METADATA: ViewMetadata<BookStore, TargetOf_store> =
ViewMetadata<BookStore, TargetOf_store>(
newFetcher(BookStore::class).by {
name()
website()
}
) {
TargetOf_store(it)
}
}
}
@Data
public static class TargetOf_authors implements View<Author> {
...省略其他成员...
companion object {
@JvmStatic
val METADATA: ViewMetadata<Author, TargetOf_authors> =
ViewMetadata<Author, TargetOf_authors>(
newFetcher(Author::class).by {
firstName()
lastName()
gender()
}
) {
TargetOf_authors(it)
}
}
}
}
这就是本文开头说DTO语言本质上是对象抓取器的另外一种表达方式的原因
使用自定义查询
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<Book> books = sqlClient
.createQuery(table)
.where(table.name().eq("GraphQL in Action"))
.orderBy(table.name())
.orderBy(table.edition().desc())
.select(
table.fetch(BookDetailView.class)
)
.execute()
val books = sqlClient
.createQuery(Book::class) {
where(table.name eq "GraphQL in Action")
orderBy(table.name)
orderBy(table.edition.desc())
select(
table.fetch(BookDetailView::class)
)
}
.execute()
这里,我们看到了,以前代码中的table.fetch(fetcher)
被替换成了table.fetch(BookDetailView.class)
。
所有底层查询API都可以用viewType
替换fetcher
,再次印证了本文开头所说DTO语言本质上是对象抓取器的另外一种表达方式。