基本用法
简介
保存指令允许开发人员保存任意形状的数据结构,而非保存简单的对象。
在默认情况下,即在AssociatedSaveMode为REPLACE情况下,Jimmer会用被保存结构去全量替换数据库中已有的数据结构,如图所示:
右上角: 用户传入一个任意形状的数据结构,让Jimmer写入数据库。
左上角: 从数据库中查询已有的数据结构,用于和用户传入的新数据结构对比。
用户传入什么形状的数据结构,就从数据查询什么形状的数据结构,新旧数据结构的形状完全一致。所以,查询成本和对比成本由用户传入的数据结构的复杂度决定。
下方: 对比新旧数据结构,找出DIFF并执行相应的SQL操作,让新旧数据一致:
- 橙色部分:对于在新旧数据结构中存在的实体对象,如果某些标量属性发生变化,修改数据
- 蓝色部分:对于在新旧数据结构中存在的实体对象,如果某些关联发生变化,修改关联
- 绿色部分:对于在新数据结构中存在但在旧数据结构中不存在实体对象,插入数据并建立关联
- 红色部分:对于在旧数据结构中存在但在新数据结构中不存在实体对象,对此对 象进行脱钩,清除关联并有可能删除数据
和其他ORM不同,Jimmer无需在实体模型上描述数据如何保存
- 某些标量属性是否需要被保存以JPA为例,通过Column.insertable和Column.updatable控制。
- 某些关联属性是否需要被保存
Jimmer采用完全不同的策略,其实体对象并非POJO,可以灵活地控制数据结构的形状。
即,实体对象具备动态性,不为实体对象指定某个属性和将实体的某个属性指定为null,是完全不同的事情。
对于任何一个实体对象而言,Jimmer只会保存被指定的属性,而忽略未指定的属性。
因此,Jimmer无需在实体建模时考虑数据的保存行为,而是在运行时通过被保存的数据结构自身来描述期望的行为,具备绝对的灵活性。
1. 单个实体对象的灵活性
1.1. 灵活控制简单属性是否被修改
让我们先来看Jimmer如何区分如下两种需求
-
不更新对象的某个属性
-
将对象的某个属性更新为null
对于传统的静态语言ORM而言,这是一个非常棘手的问题。但是Jimmer可以轻松区分这两种不同的行为。
1.1.1. 不更新对象的某个属性
- Java
- Kotlin
sqlClient.update(
Immutables.createBook(draft -> {
draft.setId(8L);
draft.setPrice(new BigDecimal("33.9"));
// 并未指定`store`或`storeId属性`
})
);
sqlClient.update(
Book {
id = 8L
price = BigDecimal("33.9")
// 并未指定`store`或`storeId属性`
}
);
生成如下SQL
update BOOK
set
PRICE = ? /* 33.9 */
where
ID = ? /* 8 */
可见,只修改被指定的字段PRICE
,并未修改其他未指定的字段 (包括STORE_ID
)。
1.1.2. 将对象的某个属性更新为null
- Java
- Kotlin
sqlClient.update(
Immutables.createBook(draft -> {
draft.setId(8L);
draft.setPrice(BigDecimal("33.9"))
draft.setStore(null);
// 也可写作`draft.setStoreId(null)`
})
);
sqlClient.update(
Book {
id = 8L
price = BigDecimal("33.9")
store = null
// 也可写作`storeId = null`
}
);
生成如下SQL
update BOOK
set
PRICE = ? /* 33.9 */
STORE_ID = ? /* <null: long> */
where
ID = ? /* 8 */
1.2. 用不完整对象避免先查后改
在实际业务项目中,常常遇一个需求:更新实体的部分属性,而非所有属性。
然而,在传统ORM的开发模式中,为了省事,开发人员很少使用ORM的update语句,更多地选择先查询对象,再修改,最后保存。以JPA为例:
EntityManager entityManager = ...从当前事务上下文中获取JPA会话对象,略...
Book book = entityManager.find(Book.class, 8L);
book.setStore(null); // JPA中实体是可变的,将关联修改为null
// 这里调用merge仅为了清晰,可以不调,因为事务提交时JPA必然修改数据库
entityManager.merge(book);
其实,除了要将Book.store
修改为null外,其实这个场景对对象的其他属性完全不感兴趣。
很显然,这是一种浪费。如果实体对象具备很多属性,会更加明显。
Jimmer的实体对象并不要求制定所有属性,即,Jimmer支持不完整对象。
因此,你可以凭空捏造一个不完整的Book对象,仅指定其id
属性和store
属性,并让Jimmer直接更新即可。
- Java
- Kotlin
sqlClient.update(
Immutables.createBook(draft -> {
draft.setId(8L);
draft.setStore(null);
})
);
sqlClient.update(
Book {
id = 8L
store = null
}
);
生成如下SQL
update BOOK
set
STORE_ID = ? /* <null: long> */
where
ID = ? /* 8 */
2. 关联属性的灵活性
通过单个对象的案例,我们对保存指令的灵活性有了基本的了解。