使用DTO语言
Jimmer提供了DTO语言。
开发人员可以使用此语言快速定义要保存的Input DTO的形状,编译后
-
相关的Java/Kotlin Input DTO类将会被自动生成
-
Input DTO和实体之间的彼此转化逻辑会被自动生成
-
与之形状吻合的对象抓取器会被自动生成。(这个功能和input DTO无关,所以本文不做论述)
开发人员用自动生成的Input DTO作为API的入参,一行代码保存它即可 (内部逻辑:调用自动生成的转化逻辑把Input DTO转化为动态实体对象,再用保存指令直接保存)。
这是消除Input DTO爆炸带来的痛苦的最高效方案
定义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.dtoinput BookInput {
#allScalars(Book)
id(store)
authors {
#allScalars(Author)
-id
}
}
input SimpleBookInput { ...略... }
...省略其他Input DTO形状定义...信息用作输入参数的Input DTO的形状定义必须用
input
修饰符修饰。具体原因已经在对象篇/DTO转换/DTO语言中有详细的阐述,本文不再赘述
自动生成DTO类型
Jimmer负责编译dto文件,自动生成符合这些形状的DTO 类型。
如果除了dto文件外还有其他Java/Kotlin原代码文件被修改了,直接点击IDE中运行按钮可以导致dto文件的重新编译
但是,如果除了dto文件外没有其他Java/Kotlin文件被修改,简单地点击IDE中运行按钮并不会导致dto文件被重新编译,除非显式地rebuild!
如果你使用的构建工具是Gradle,也可以使用社区提供的第三方Gradle插件来解决这个问题: jimmer-gradle
以上面代码中的BookInput
为例,此dto文件被Jimmer成功编译后,会自动生成如下DTO类型
- Java
- Kotlin
package com.yourcompany.yourproject.model.dto;
import com.yourcompany.yourproject.model.Book;
import org.babyfish.jimmer.Input;
@GeneratedBy(file = "<your_project>/src/main/dto/Book.dto")
public class BookInput implements Input<Book> {
@Nullable
private Long id; ❶
private String name;
private int edition;
private BigDecimal price;
@Nullable
private Long storeId; ❷
private List<TargetOf_authors> authors; ❸
public BookInput(Book book) { ❹
...略...
}
@Override
public Book toEntity() { ❺
...略...
}
@Data
public static class TargetOf_authors {
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.Input
@GeneratedBy(file = "<your_project>/src/main/dto/Book.dto")
data class BookInput(
val id: Long? = null, ❶
val name: String = "",
val edition: Int = 0,
val price: BigDecimal,
val storeId: Long? = null, ❷
val authors: List<TargetOf_authors> = emptyList() ❸
): Input<Book> {
constructor(book: Book) : this(...略...) ❹
override fun toEntity(): Book = ...略... ❺
data class TargetOf_authors(
val firstName: String,
val lastName: String,
val gender: Gender
) {
...省略其他成员...
}
...省略其他成员...
}
-
❶ 如果id被指定了自动生成策略,则id不是必须的。这也是保存指令的一个特性,具体细节请参考保存模式/总结
信息-
对于Jimmer实体而言,
id
不可能为null,靠id属性的缺失 (即,不赋值) 来表达对象没有id的情况。 -
对于Input DTO而言,静态的POJO类型没有属性缺失的概念,靠null来表达没有id的情况。
二者看似矛盾,但其实可以简单地处理:如果Input DTO的id为null,转化后的实体对象无id (虽然id不允许被赋为null,但动态对象可以不对属性赋值)
-
-
❷ 明确指定此InputDTO想以
的方式编辑实体的多对一关联Book.store
。其中, -
❸ 明确指定此InputDTO想以
的方式编辑实体的多对过关联Book.authors
, 关联对象的类型也被内嵌的InputDTO类型BookInput.TargetOf_authors
固化。 -
❹ 将动态实体转化为静态InputDTO
-
❺ 将静态InputDTO转化为动态实体
HTTP API
DTO语言生成了相对完备的代码,所以,我们可以快速对外暴露保存数据的HTTP API
- Java
- Kotlin
@PutMapping("/book")
public void saveBook(
@RequestBody BookInput input
) {
// `save(input)`等价于`save(input.toEntity())`
bookRepository.save(input);
}
@PutMapping("/book")
fun saveBook(
@RequestBody input: BookInput
) {
// `save(input)`等价于`save(input.toEntity())`
bookRepository.save(input)
}