保存关联
关联类型
站在用户角度,被保存数据结构中存在两种关联
短关联
所谓短关联,表示仅修改当前对象和其他对象之间的关联本身,对关联对象的修改没兴趣。
通常,UI设计会采用单选框 (关联引用) 或多选框 (关联集合)。
Book Form
其中
-
单选框对应多对一关联
Book.store
-
多选框对应多对多关联
Book.authors
实际项目中,待选数据可能很多,并不适合设计为下拉UI。这时,可以使用具备筛选条件和分页功能的对象框来代替下拉框,这是一种常见的变通方式。
由于用户只想修改当前对象和其他对象的关联,不想进一步修改关联对象,所以UI不可能出现多层关联嵌套。这就是称其为 短关联 的原因。
为save指令传递任意形状的数据结构作为参数时,指定短关联有两种方法
-
要么让关联对象只有id属性
-
要么让关联对象只有key属性
两种方法示范如下
-
让关联对象只有id属性
- Java
- Kotlin
Book book = Objects.createBook(draft -> {
draft.setName("SQL in Action");
draft.setEdition(1);
draft.setPrice(new BigDecimal("39.9"));
draft.applyStore(store -> {
// 关联对象只有id属性
store.setId(2L);
});
draft.addIntoAuthors(author -> {
// 关联对象只有id属性
author.setId(4L);
});
draft.addIntoAuthors(author -> {
// 关联对象只有id属性
author.setId(5L);
});
});
sqlClient.save(book);val book = new(Book::class).by {
name = "SQL in Action"
edition = 1
price = BigDecimal("39.9")
store {
// 关联对象只有id属性
id = 2L
}
authors().addBy {
// 关联对象只有id属性
id = 4L
}
authors().addBy {
// 关联对象只有id属性
id = 5L
}
}
sqlClient.save(book)备注这里对被保存数据结构进行硬编码仅为示范,实际项目中被保存的数据结构由前端界面提交。
当然,如果用户按照映射篇/进阶映射/视图属性/IdView一文的方法定义了
authorIds
属性,上述代码可以被简化,例如:draft.setAuthorIds(Arrays.asList(4L, 5L));
为了让例子更具普适性,这里并不假设用户为实体类型定义了IdView属性。后续所有文档都如此,不再提醒。
-
让关联对象只有key属性
备注下面代码,假设
-
BookStore
类型的key属性是name
-
Author
类型的key属性是firstName
和lastName
实际项目中,这个唯一性约束并不合理,这里为简化例子,姑且这样假设,请读者对此细节别太较真
- Java
- Kotlin
Book book = Objects.createBook(draft -> {
draft.setName("SQL in Action");
draft.setEdition(1);
draft.setPrice(new BigDecimal("39.9"));
draft.applyStore(store -> {
// 关联对象只有key属性,即`BookStore.name`
store.setName("MANNING");
});
draft.addIntoAuthors(author -> {
// 关联对象只有key属性,即`Author.firstName`和`Author.lastName`
author.setFirstName("Boris").setLastName("Cherny");
});
draft.addIntoAuthors(author -> {
// 关联对象只有key属性,即`Author.firstName`和`Author.lastName`
author.setFirstName("Samer").setLastName("Buna");
});
});
sqlClient.save(book);val book = new(Book::class).by {
name = "SQL in Action"
edition = 1
price = BigDecimal("39.9")
store {
// 关联对象只有key属性,即`BookStore.name`
name = "MANNING"
}
authors().addBy {
// 关联对象只有key属性,即`Author.firstName`和`Author.lastName`
firstName = "Boris"
lastName = "Cherny"
}
authors().addBy {
// 关联对象只有key属性,即`Author.firstName`和`Author.lastName`
firstName = "Samer"
lastName = "Buna"
}
}
sqlClient.save(book) -
长关联
所谓长关联,表示除了要修改当前对象和其他对象之间的关联本身外,还要进一步修改关联对象。
通常,UI设计会采用内嵌表格,例如
由于用户不但要修改当前对象和其他对象的关联,还要进一步修改关联对象,而关联对象可以包含更深的关联,所以,理论上讲,UI可出多层关联嵌套。这就是称其为 长关联 的原因。
虽然设计人员为了保证UI的简洁性会刻意避免在内嵌表格中嵌套更深的内嵌表格,但是实际项目中仍然存在需要在UI上维护多层嵌套关联的场景,比如:
-
表单本身是一颗树结构,编辑好了后,作为一个整体保存。
-
可视化UI设计,因为UI组件本身就是树形结构,用户进行一系列可视化拖拉拽的设计后,把UI组件树作为一个整体保存。
为save指令传递任意形状的数据结构作为参数时,指定长关联有两种方法
-
如果已经为关联对象指定了id属性,继续为对象指定任何非id属性 (包括key属性)
-
否则,必须为关联对象指定了所有key属性和其他属性。
无论是以上哪种方式,至少为关联对象指定一个既非id也非key的属性。
例子如下
- Java
- Kotlin
Order order = Objects.createOrder(draft -> {
draft.applyCustomer(customer -> customer.setId(1L));
draft.setProvince("四川");
draft.setCity("成都");
draft.setAddress("龙泉驿区洪玉路与十洪路交叉口");
draft.addIntoItems(item -> {
item.applyProduct(product -> product.setId(1L));
// 既非id也非key的属性
item.setQuantity(2);
});
draft.addIntoItems(item -> {
item.applyProduct(product -> product.setId(10L));
// 既非id也非key的属性
item.setQuantity(1);
});
});
sqlClient.save(order);
val order = new(Order::class).by {
customer { id = 1L }
province = "四川"
city = "成都"
address = "龙泉驿区洪玉路与十洪路交叉口"
items().addBy {
product { id = 1L }
// 既非id也非key的属性
quantity = 2
}
items().addBy {
product { id = 10L }
// 既非id也非key的属性
quantity = 1
}
}
sqlClient.save(order)
这里对被保存数据结构进行硬编码仅为示范,实际项目中被保存的数据结构由前端界面提交。
一对多关联的特殊性
之前提到
如果实体类型声明了Key属性 (也意味着id除了充当唯一标识符外无任何业务意义),那么
-
对于被保存的聚合根对象而言,建议为其指定id属性或key属性。否则会被理解成强行插入。
-
对于本文正在讨论的待保存的关联对象而言,要么指定id属性,要么指定所有key属性。否则会异常。
但是,有一种情况例外,一对多关系。
让我来看一个例子
实体定义
- Java
- Kotlin
@Entity
public interface TreeNode {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();
@Key
String name();
@Key
@Nullable
@ManyToOne
TreeNode parent();
...省略其他属性...
}
@Entity
public interface TreeNode {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long
@Key
val name: String
@Key
@ManyToOne
val parent: TreeNode?
...省略其他属性...
}