动态性
本文的示例代码使用了一个名为BookDraft
的类型,该接口是Jimmer根据用户定义的类型Book
自动生成的接口类型。
读者可先行忽略这个自动生成的接口,后续文档Draft会对其做出介绍
基本概念
1. 较少属性
- Java
- Kotlin
Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
});
val book = Book {
name = "Learning GraphQL"
}
这时,book
对象对应的JSON如下
{"name": "Learning GraphQL"}
2. 较多属性
- Java
- Kotlin
Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
});
val book = Book {
name = "Learning GraphQL"
edition = 1
price = BigDecimal("49.99")
}
这时,book
对象对应的JSON如下
{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99
}
3. 包含关联
- Java
- Kotlin
Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
draft.applyStore(store -> {
store.setName("O'REILLY");
store.setWebsite("https://www.oreilly.com/");
});
});
val book = Book {
name = "Learning GraphQL"
edition = 1
price = BigDecimal("49.99")
store {
name = "O'REILLY"
website = "https://www.oreilly.com/"
}
}
这时,book
对象对应的JSON如下
{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"store": {
"name": "O'REILLY",
"website": "https://www.oreilly.com/"
}
}
4. 更多关联 (更大广度)
- Java
- Kotlin
Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
draft.applyStore(store -> {
store.setName("O'REILLY");
store.setWebsite("https://www.oreilly.com/");
});
draft.addIntoAuthors(author -> {
author.setFirstName("Eve");
author.setLastName("Procello");
author.setGender(Gender.FEMALE);
});
draft.addIntoAuthors(author -> {
author.setFirstName("Alex");
author.setLastName("Banks");
author.setGender(Gender.MALE);
});
});
val book = Book {
name = "Learning GraphQL"
edition = 1
price = BigDecimal("49.99")
store {
name = "O'REILLY"
website = "https://www.oreilly.com/"
}
authors().addBy {
firstName = "Eve"
setLastName = "Procello"
gender = Gender.FEMALE
}
authors().addBy {
firstName = "Alex"
lastName = "Banks"
gender = Gender.MALE
}
}
这时,book
对象对应的JSON如下
{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"store": {
"name": "O'REILLY",
"website": "https://www.oreilly.com/"
},
"authors": [
{
"firstName": "Eve",
"lastName": "Procello",
"gender:" "FEMALE"
},
{
"firstName": "Alex",
"lastName": "Banks",
"gender:" "MALE"
}
]
}
5. 更深关联 (更大深度)
和前面的例子不同,这里,我们选择用BookStore
作为数据结构的聚合根,而非Book
。
- Java
- Kotlin
BookStore store = Immutables.createBookStore(draft -> {
draft.setName("O'REILLY");
draft.setWebsite("https://www.oreilly.com/");
draft.addIntoBooks(book -> {
book.setName("Learning GraphQL");
book.setEdition(1);
book.setPrice(new BigDecimal("49.99"));
book.addIntoAuthors(author -> {
author.setFirstName("Eve");
author.setLastName("Procello");
author.setGender(Gender.FEMALE);
});
book.addIntoAuthors(author -> {
author.setFirstName("Alex");
author.setLastName("Banks");
author.setGender(Gender.MALE);
});
});
});
val store = BookStore {
name = "O'REILLY"
website = "https://www.oreilly.com/"
books().addBy {
name = "Learning GraphQL"
edition = 1
price = BigDecimal("49.99")
authors().addBy {
firstName = "Eve"
setLastName = "Procello"
gender = Gender.FEMALE
}
authors().addBy {
firstName = "Alex"
lastName = "Banks"
gender = Gender.MALE
}
}
}
这时,store
对象对应的JSON如下
{
"name": "O'REILLY",
"website": "https://www.oreilly.com/",
"books": [
{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"authors": [
{
"firstName": "Eve",
"lastName": "Procello",
"gender:" "FEMALE"
},
{
"firstName": "Alex",
"lastName": "Banks",
"gender:" "MALE"
}
]
}
]
}
动态对象的特性
动态对象可能缺失任何属性,也可以换一种说法,动态对象并不要求其所有属性都被设置。
在动态对象中,属性缺失和属性被设置为null是完全不同的两回事。
-
属性缺失: 对象该属性的值未知,当前业务对其不感兴趣
-
属性被设置为null: 对象该属性的值已知,真的是啥都没有
而在静态的POJO中,二者其实是无法区分的。更糟的是,开发人员常借助于Java语言不支持null safety的特性,有意或无意地混淆二者。
动态对象这个概念非常重要,是理解Jimmer的关键!
对于缺失的属性而言
-
使用代码直接访问,将会得到异常
org.babyfish.jimmer.UnloadedException
备注如果读者有Hibernate经历,可以将其理解为
org.hibernate.LazyInitializationException
-
在Jackson序列化中,将会被自动忽略。
信息这要求对Jackson做一点配置。由于这个细节非常重要,已经被独立成一篇文档,请参阅和Jackson协同
和ORM的互动
动态性是Jimmer对象的内禀 特征,具有普适性,ORM框架本身和开发人员双方都可以轻松构建动态对象给对方使用。
对象抓取器
Jimmer创建动态对象,返回给开发人员
- Java
- Kotlin
- GraphQL基于HTTP服务,该功能只有在跨越HTTP服务的边界才能呈现;而在Jimmer中,这是ORM的基础API,你可以在任何代码逻辑中使用此能力。
- 截止到目前为止,GraphQL协议不支持对深度无限的自关联属性的递归查询;而Jimmer支持。
保存指令
开发人员创建动态对象,传递给Jimmer
右上角: 用户传入一个任意形状的数据结构,让Jimmer写入数据库。
这和其他ORM框架的save方法之间存在本质差异。 以JPA/Hibernate为例,对象的普通属性是否需要被保存通过Column.insertable和Column.updatable控制, 关联属性是否需要被保存通过OneToOne.cascade、ManyToOne.cascade、OenToMany.cascade和ManyToOne.cascade控制。 然而,无论如何开发人员如何配置,JPA/Hibernate能够为你保存的数据结构的形状是固定的。
Jimmer采用完全不同方法,被保存的Jimmer对象虽然是强类型的,但具备动态性 (即, 不设置对象属性和把对象对象属性设置为null是完全同的两码事), 被设置的属性会被保存,而未被设置的属性会被忽略,这样,就可以保存任意形状的数据结构。
左上角: 从数据库中查询已有的数据结构,用于和用户传入的新数据结构对比。
用户传入什么形状的数据结构,就从数据查询什么形状的数据结构,新旧数据结构的形状完全一致。所以,查询成本和对比成本由用户传入的数据结构的复杂度决定。
下方: 对比新旧数据结构,找出DIFF并执行相应的SQL操作,让新旧数据一致:
- 橙色部分:对于在新旧数据结构中存在的实体对象,如果某些标量属性发生变化,修改数据
- 蓝色部分:对于在新旧数据结构中存在的实体对象,如果某些关联发生变化,修改关联
- 绿色部分:对于在新数据结构中存在但在旧数据结构中不存在实体对象,插入数据并建立关联
- 红色部分:对于在旧数据结构中存在但在新数据结构中不存在实体对象,对此对象进行脱钩,清除关联并有可能删除数据