删除指令
基本概念
删除指令,即按id或id集合删除对象。
| API类别 | 语言 | 按id删除 | 按id集合删除 |
|---|---|---|---|
| 底层API | Java | 完整API
快捷API
| 完整API
快捷API
|
| Kotlin | 完整API
快捷API
| 完整API
快捷API
| |
| Spring Data API | Java |
|
|
| Kotlin |
|
|
必要说明
-
在Java底层API中,具备两个方法以Command结尾的方法:
deleteCommand和deleteAllCommand。-
这两个方法创建指令但不立即执行,用户对指令做出更多配置后,在调用
execute执行。以
deleteCommand为例DeleteResult result = sqlClient
.getEntities()
.deleteCommand(BookStore.class, 1L) ❶
.setDissociateAction(BookProps.STORE, DissociateAction.SET_NULL) ❷
.execute(); ❸-
❶ 创建指令,但不执行
-
❷ 配置指令,可以用链式API进行多个配置 (这里仅仅示范了一个配置)
信息该配置的作用会在后文阐述,读者可以先忽略它
-
❸最终执行
kotlin不需要如此设计,因为其delete方法支持一个可选的Lambda参数用于配置,直接执行并附带上必要配置即可。
val result = sqlClient
.entities
.delete(BookStore.class, 1L) {
setDissociateAction(Book::store, DissociateAction.SET_NULL)
} -
-
-
在Spring Data API中,我们可以找到两个功能相同但名称不同的方法:
deleteByIds和deleteAllById。-
deleteByIds: 和Jimmer底层快捷API风格一致的方法 -
deleteAllById:继承org.springframework.data.repository.CrudRepository后必需拥有方法,可以理解成deleteByIds的别名。
-
-
删除指令支持两种操作
-
逻辑删除:并非真正删除数据,仅把对象的逻辑删除字段标记成“已经删除”。
-
物理删除:真正删除数据。
上述所有API,都通过Java方法重载或Kotlin默认参数支持一个可选的参数,其类型为
DeleteMode,该参数类型为枚举,具有三个取值 -
逻辑删除
假设实体具备逻辑删除字段,例如
- Java
- Kotlin
@Entity
public interface Book {
@LogicalDeleted("true")
boolean isDeleted();
...省略其他代码...
}
@Entity
interface Book {
@LogicalDeleted("true")
val isDeleted: Boolean
...省略其他代码...
}
那么以下三种行为
sqlClient.deleteById(Book.class, 1L)sqlClient.deleteById(Book.class, 1L, DeleteMode.AUTO)sqlClient.deleteById(Book.class, 1L, DeleteMode.LOGICAL)
的功能一样,都表示逻辑删除,生成的sql如下
update BOOK
set DELETED = ? /* true */
where ID in(?/* 1L*/)
物理删除
如果实体不具备逻辑删除字段,或删除模式被强行指定为DeleteMode.PHYSICAL,则执行物 理删除。
基本用法
- Java
- Kotlin
DeleteResult result = sqlClient
.getEntities()
.deleteAll(Book.class, Arrays.asList(1L, 2L, 3L, 4L));
System.out.println(
"Affected row count: " +
result.getTotalAffectedRowCount() +
"\nAffected row count of table 'BOOK': " +
result.getAffectedRowCount(AffectedTable.of(Book.class)) +
"\nAffected row count of middle table 'BOOK_AUTHOR_MAPPING': " +
result.getAffectedRowCount(AffectedTable.of(BookProps.AUTHORS))
);
val result = sqlClient
.entities
.deleteAll(Book::class, listOf(1L, 2L, 3L, 4L))
println(
"""Affected row count:
|${result.totalAffectedRowCount}
|Affected row count of table 'BOOK':
|${result.affectedRowCount(Book::class)}
|Affected row count of middle table 'BOOK_AUTHOR_MAPPING':
|${result.affectedRowCount(Book::authors)}
""".trimMargin()
)
最终生成的SQL如下
-
delete from BOOK_AUTHOR_MAPPING
where BOOK_ID in(?, ?, ?, ?) -
delete from BOOK
where ID in(?, ?, ?, ?)
脱勾模式
从上面的论述可以看到,delete指令有可能导致多对多关联中间表的数据的被删除,这是 比较简单的情况。
对于直接基于外键的一对一或一对多关联而言,需要处理的情况更复杂一些。
- Java
- Kotlin
DeleteResult result = sqlClient
.getEntities()
.delete(BookStore.class, 1L);
System.out.println(
"Affected row count: " +
result.getTotalAffectedRowCount() +
"\nAffected row count of table 'BOOK_STORE': " +
result.getAffectedRowCount(AffectedTable.of(BookStore.class)) +
"\nAffected row count of table 'BOOK': " +
result.getAffectedRowCount(AffectedTable.of(Book.class)) +
"\nAffected row count of middle table 'BOOK_AUTHOR_MAPPING': " +
result.getAffectedRowCount(AffectedTable.of(BookProps.AUTHORS))
);
val result = sqlClient
.entities
.delete(BookStore::class, 1L)
println(
"""Affected row count:
|${result.totalAffectedRowCount}
|Affected row count of table 'BOOK_STORE':
|${result.affectedRowCount(BookStore::class)}
|Affected row count of table 'BOOK':
|${result.affectedRowCount(Book::class)}
|Affected row count of middle table 'BOOK_AUTHOR_MAPPING':
|${result.affectedRowCount(Book::authors)}
""".trimMargin()
)
这段代码删除一个BookStore对象。
由于BookStore对象存在一对多关联BookStore.books,如果被删除的对象在数据库中已经存在一些关联对象,Jimmer将抛弃这些对象。
一对多关联BookStore.books不是基于中间表的映射,而是基于外键映射。Jimmer将如何抛弃这些Book对象呢?
和JPA不同,Jimmer不允许直接使用@OneToMany进行关联映射,@OneToMany必须使用mappedBy属性。可以参考@OneToMany以了解更多。
这表示,通过一对多关联BookStore.books一定能找到与之对应的多对一关联Book.store。
接下来,Jimmer会参考多对一关联属性Book.store上的@OnDissociate注解。
子对象脱勾操作有5种模式
| 模式 | 描述 |
|---|---|
NONE (默认) | 视全局配置jimmer.default-dissociate-action-checking而定
|
| LAX | 脱钩操作不执行任何动作。
|
| CHECK | 不支持脱钩操作,如果数据库中当前父对象拥有需要脱钩的子对象,则抛出异常阻止操作。 |
| SET_NULL | 把被脱勾的子对象的外键设置为null。使用此模式的前提是子对象的外键关联属性是nullnullable的;否则尝试此配置将会导致异常。 |
| DELETE | 将被脱勾的子对象删除。 |
脱钩示范
-
如果
Book.store所对应的外键被@OnDissociate注解配置为SET_NULL,则,执行如下SQLupdate BOOK set STORE_ID = null where STORE_ID in(?)其中参数为被删除对象的id。这样,这些被抛弃对象的外键就被设置为null了。
-
否则,则先执行
select ID from BOOK where STORE_ID in(?)其中参数为被删除对象的id。这样,就得到这些被抛弃对象的id了。
如果查询没有返回任何数据,就忽略后续步骤。
-
如果
Book.store所对应的外键被@OnDissociate注解配置为DELETE, 运用新的delete指令删除这些被抛弃对象,其实这就是delete指令的自动递归执行能力。 -
否则,抛出异常。
-
上面所讨论的这些情况,都需要开发人员在Book.store属性上使用注解@OnDissociate。
然而,你也可以选择不使用@OnDissociate注解,而动态地为delete指令指定dissociateAction配置。
- Java
- Kotlin
DeleteResult result = sqlClient
.getEntities()
.deleteCommand(BookStore.class, 1L)
.configure(it ->
it
.setDissociateAction(
BookProps.STORE,
DissociateAction.SET_NULL
)
)
.execute();
val result = sqlClient
.entities
.delete(BookStore::class, 1L) {
setDissociateAction(
Book::store,
DissociateAction.SET_NULL
)
}
这里,动态地调用指令的setDissociateAction方法,相比于静态地在Book.store属性上使用注解@OnDissociate并指定级联删除,效果完全一样。
-
如果
setDissociateAction方法最后一个参数为DissociateAction.SET_NULL,则被设置关联属性必须可空,否则会导致异常。 -
如果既动态地为save指令配置了删除规则,又静态地在实体接口中通过注解@OnDissociate配置了删除规则,则动态配置优先。