跳到主要内容

删除指令

基本概念

删除指令,即按id或id集合删除对象。

API类别语言按id删除按id集合删除
底层APIJava

完整API

  • JSqlClient.getEntities().delete
  • JSqlClient.getEntities().deleteCommand

快捷API

  • JSqlClient.deleteById

完整API

  • JSqlClient.getEntities().deleteAll
  • JSqlClient.getEntities().deleteAllCommand

快捷API

  • JSqlClient.deleteByIds
Kotlin

完整API

  • KSqlClient.entities.delete

快捷API

  • KSqlClient.deleteById

完整API

  • KSqlClient.entities.deleteAll

快捷API

  • KSqlClient.deleteByIds
Spring Data APIJava
  • JRepository.deleteById
  • JRepository.deleteByIds
  • JRepository.deleteAllById
Kotlin
  • KRepository.deleteById
  • KRepository.deleteByIds
  • KRepository.deleteAllById

必要说明

  • 在Java底层API中,具备两个方法以Command结尾的方法: deleteCommanddeleteAllCommand

    • 这两个方法创建指令但不立即执行,用户对指令做出更多配置后,在调用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中,我们可以找到两个功能相同但名称不同的方法:deleteByIdsdeleteAllById

    • deleteByIds: 和Jimmer底层快捷API风格一致的方法

    • deleteAllById:继承org.springframework.data.repository.CrudRepository后必需拥有方法,可以理解成deleteByIds的别名。

  • 删除指令支持两种操作

    • 逻辑删除:并非真正删除数据,仅把对象的逻辑删除字段标记成“已经删除”。

    • 物理删除:真正删除数据。

    上述所有API,都通过Java方法重载或Kotlin默认参数支持一个可选的参数,其类型为DeleteMode,该参数类型为枚举,具有三个取值

    • AUTO: 不明确指定当前操作是逻辑删除还是物理删除

      • 如果被删除的实体类型具备逻辑删除字段,则执行逻辑删除

      • 否则,执行物理删除

    • LOGICAL: 明确指定当前操作是逻辑删除

      • 如果被删除的实体类型具备逻辑删除字段,则执行逻辑删除

      • 否则,抛出异常,说明当前实体不支持逻辑删除

    • PHYSICAL: 明确指定当前操作是物理删除

      无论被删除的实体类型是否具备逻辑删除字段,都执行物理删除

逻辑删除

假设实体具备逻辑删除字段,例如

Book.java
@Entity
public interface Book {

@LogicalDeleted("true")
boolean isDeleted();

...省略其他代码...
}

那么以下三种行为

  • 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,则执行物理删除。

基本用法

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

最终生成的SQL如下

  1. delete from BOOK_AUTHOR_MAPPING 
    where BOOK_ID in(?, ?, ?, ?)
  2. delete from BOOK 
    where ID in(?, ?, ?, ?)

脱勾模式

从上面的论述可以看到,delete指令有可能导致多对多关联中间表的数据的被删除,这是比较简单的情况。

对于直接基于外键的一对一或一对多关联而言,需要处理的情况更复杂一些。

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

这段代码删除一个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。

即便存在子对象,也支持脱钩操作。即使发生父对象被删除的情况 (脱钩模式也被删除指令采用),也任由子对象的伪外键发生悬挂问题 (即便伪外键发生悬挂,查询系统也可以正常工作)

CHECK如果存在子对象,则不支持脱勾操作,通过抛出异常阻止操作。
SET_NULL把被脱勾的子对象的外键设置为null。前提是子对象的多对一关联属性是nullable的;否则尝试此配置将会导致异常。
DELETE将被脱勾的子对象删除。

脱钩示范

  1. 如果Book.store所对应的外键被@OnDissociate注解配置为SET_NULL,则,执行如下SQL

    update BOOK set STORE_ID = null where STORE_ID in(?)

    其中参数为被删除对象的id。这样,这些被抛弃对象的外键就被设置为null了。

  2. 否则,则先执行

    select ID from BOOK where STORE_ID in(?)

    其中参数为被删除对象的id。这样,就得到这些被抛弃对象的id了。

    如果查询没有返回任何数据,就忽略后续步骤。

    • 如果Book.store所对应的外键被@OnDissociate注解配置为DELETE, 运用新的delete指令删除这些被抛弃对象,其实这就是delete指令的自动递归执行能力。

    • 否则,抛出异常。

上面所讨论的这些情况,都需要开发人员在Book.store属性上使用注解@OnDissociate

然而,你也可以选择不使用@OnDissociate注解,而动态地为delete指令指定dissociateAction配置。

DeleteResult result = sqlClient
.getEntities()
.deleteCommand(BookStore.class, 1L)
.configure(it ->
it
.setDissociateAction(
BookProps.STORE,
DissociateAction.SET_NULL
)
)
.execute();

这里,动态地调用指令的setDissociateAction方法,相比于静态地在Book.store属性上使用注解@OnDissociate并指定级联删除,效果完全一样。

信息
  1. 如果setDissociateAction方法最后一个参数为DissociateAction.SET_NULL,则被设置关联属性必须可空,否则会导致异常。

  2. 如果既动态地为save指令配置了删除规则,又静态地在实体接口中通过注解@OnDissociate配置了删除规则,则动态配置优先。