跳到主要内容

动态性

信息

本文的示例代码使用了一个名为BookDraft的类型,该接口是Jimmer根据用户定义的类型Book自动生成的接口类型。

读者可先行忽略这个自动生成的接口,后续文档Draft会对其做出介绍

基本概念

1. 较少属性

Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
});

这时,book对象对应的JSON如下

{"name": "Learning GraphQL"}

2. 较多属性

Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
});

这时,book对象对应的JSON如下

{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99
}

3. 包含关联

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/");
});
});

这时,book对象对应的JSON如下

{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"store": {
"name": "O'REILLY",
"website": "https://www.oreilly.com/"
}
}

4. 更多关联 (更大广度)

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

这时,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

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

这时,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创建动态对象,返回给开发人员

    即,查询任意形状的数据结构。此功能叫对象抓取器

  • 开发人员创建动态对象,传递给Jimmer

    即,保存任意形状的数据结构。此功能叫保存指令

对象抓取器

Jimmer创建动态对象,返回给开发人员

信息
和GraphQL比较
  • GraphQL基于HTTP服务,该功能只有在跨越HTTP服务的边界才能呈现;而在Jimmer中,这是ORM的基础API,你可以在任何代码逻辑中使用此能力。
  • 截止到目前为止,GraphQL协议不支持对深度无限的自关联属性的递归查询;而Jimmer支持。

保存指令

开发人员创建动态对象,传递给Jimmer

  • 右上角: 用户传入一个任意形状的数据结构,让Jimmer写入数据库。

    这和其他ORM框架的save方法之间存在本质差异。 以JPA/Hibernate为例,对象的普通属性是否需要被保存通过Column.insertableColumn.updatable控制, 关联属性是否需要被保存通过OneToOne.cascadeManyToOne.cascadeOenToMany.cascadeManyToOne.cascade控制。 然而,无论如何开发人员如何配置,JPA/Hibernate能够为你保存的数据结构的形状是固定的。

    Jimmer采用完全不同方法,被保存的Jimmer对象虽然是强类型的,但具备动态性 (即, 不设置对象属性和把对象对象属性设置为null是完全同的两码事), 被设置的属性会被保存,而未被设置的属性会被忽略,这样,就可以保存任意形状的数据结构。

  • 左上角: 从数据库中查询已有的数据结构,用于和用户传入的新数据结构对比。

    用户传入什么形状的数据结构,就从数据查询什么形状的数据结构,新旧数据结构的形状完全一致。所以,查询成本和对比成本由用户传入的数据结构的复杂度决定。

  • 下方: 对比新旧数据结构,找出DIFF并执行相应的SQL操作,让新旧数据一致:

    • 橙色部分:对于在新旧数据结构中存在的实体对象,如果某些标量属性发生变化,修改数据
    • 蓝色部分:对于在新旧数据结构中存在的实体对象,如果某些关联发生变化,修改关联
    • 绿色部分:对于在新数据结构中存在但在旧数据结构中不存在实体对象,插入数据并建立关联
    • 红色部分:对于在旧数据结构中存在但在新数据结构中不存在实体对象,对此对象进行脱钩,清除关联并有可能删除数据
提示

此功能的目的:把任意形状的数据结构作为一个整体,使用一行代码写入数据库,无论中间细节多复杂,都不用关心。

如果你了解Web领域的ReactVue,不难看出这个功能很像`Virtual DOM diff`。