# 逻辑删除 ## 逻辑删除简介 逻辑删除指的是在删除数据的时候,并非真正的去删除,而是将表中列所对应的状态字段(status)做修改操作, 实际上并未删除目标数据。 我们可以进行表的字段设计时,用一个列标识该数据的 "删除状态",在 mybatis-flex 中,正常状态的值为 0, 已删除 的值为 1(可以通过设置 FlexGlobalConfig 来修改这个值)。 ## MyBatis-Flex 逻辑删除示例 假设在 tb_account 表中,存在一个为 is_deleted 的字段,用来标识该数据的逻辑删除,那么 tb_account 表 对应的 "Account.java" 实体类应该配置如下: ```java @Table("tb_account") public class Account { @Column(isLogicDelete = true) private Boolean isDelete; //Getter Setter... } ``` 此时,当我们执行如下的删除代码是: ```java accountMapper.deleteById(1); ``` MyBatis 执行的 SQL 如下: ```sql UPDATE `tb_account` SET `is_delete` = 1 WHERE `id` = ? AND `is_delete` = 0 ``` 可以看出,当执行 deleteById 时,MyBatis 只是进行了 update 操作,而非 delete 操作。 ## 注意事项 当 "tb_account" 的数据被删除时( is_delete = 1 时),我们通过 MyBatis-Flex 的 selectOneById 去查找数据时,会查询不到数据。 原因是 `selectOneById` 会自动添加上 `is_delete = 0` 条件,执行的 sql 如下: ```java SELECT * FROM tb_account where id = ? and is_delete = 0 ``` 不仅仅是 selectOneById 方法会添加 `is_delete = 0` 条件,BaseMapper 的以下方法也都会添加该条件: - selectOneBy** - selectListBy** - selectCountBy** - paginate 同时,比如 Left Join 或者子查询等,若 **子表也设置了逻辑删除字段**, 那么子表也会添加相应的逻辑删除条件,例如: ```java QueryWrapper query1 = QueryWrapper.create() .select() .from(ACCOUNT) .leftJoin(ARTICLE).as("a").on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID)) .where(ACCOUNT.AGE.ge(10)); ``` 其执行的 SQL 如下: ```sql SELECT * FROM `tb_account` LEFT JOIN `tb_article` AS `a` ON `a`.`is_delete` = 0 and `tb_account`.`id` = `a`.`account_id` WHERE `tb_account`.`age` >= 10 AND `tb_account`.`is_delete` = 0 ``` 在 `left join on` 条件自动添加:`a.is_delete = 0`,并在 where 条件添加上 `tb_account.is_delete = 0`。 示例 2: ```java QueryWrapper query2 = QueryWrapper.create() .select() .from(ACCOUNT) .leftJoin( //子查询 select().from(ARTICLE).where(ARTICLE.ID.ge(100)) ).as("a").on( ACCOUNT.ID.eq(raw("a.id")) ) .where(ACCOUNT.AGE.ge(10)); ``` 其执行的 SQL 如下: ```sql SELECT * FROM `tb_account` LEFT JOIN (SELECT * FROM `tb_article` WHERE `id` >= 100 AND `is_delete` = 0) AS `a` ON `tb_account`.`id` = a.id WHERE `tb_account`.`age` >= 10 AND `tb_account`.`is_delete` = 0 ``` ## 逻辑删除的默认值配置 在某些场景下,我们可能希望数据库存入的逻辑删除中的值并非 0 和 1,比如可能是 true 和 false 等,那么,我们可以通过配置 `FlexGlobalConfig` 来修改这个默认值。 如下代码所示: ```java FlexGlobalConfig globalConfig = FlexGlobalConfig.getDefaultConfig(); //设置数据库正常时的值 globalConfig.setNormalValueOfLogicDelete("..."); //设置数据已被删除时的值 globalConfig.setDeletedValueOfLogicDelete("..."); ``` ## 跳过逻辑删除处理 在某些场景下,我们再执行查询、更新或删除数据时,有必要跳过 MyBatis-Flex 自动添加的逻辑删除的相关条件, 此时,我们可以使用 LogicDeleteManager.execWithoutLogicDelete() 方法处理,代码如下: ```java LogicDeleteManager.execWithoutLogicDelete(()-> accountMapper.deleteById(1) ); ``` 以上代码中,`accountMapper` 会直接对 `Account` 数据进行物理删除,忽略逻辑删除字段配置。 ## 内置逻辑删除处理器 MyBatis-Flex 提供了三种字段类型对应的逻辑删除处理器,用户可以根据逻辑删除字段的类型进行设置,它们分别是: | 处理器名称 | 对应字段类型 | 数据正常时的值 | 数据被删除时的值 | |--------------------------------|-----------|---------|----------| | IntegerLogicDeleteProcessor | integer | 0 | 1 | | BooleanLogicDeleteProcessor | tinyint | false | true | | DateTimeLogicDeleteProcessor | datetime | null | 被删除时间 | | TimeStampLogicDeleteProcessor | bigint | 0 | 被删除时的时间戳 | | PrimaryKeyLogicDeleteProcessor | 该条数据的主键类型 | null | 该条数据的主键值 | 使用时,只需通过 `LogicDeleteManager` 来设置逻辑删除处理器即可,例如: ```java LogicDeleteManager.setProcessor(new DateTimeLogicDeleteProcessor()); ``` ## 自定义逻辑删除处理功能 在社区中,有许多用户提出希望使用时间类型,当删除时,设置删除字段为`当前时间`,当正常时,设置为 `0` 或者 `null`。 那么,我们可以通过 `LogicDeleteManager` 设置一个新的 `LogicDeleteProcessor`: `LogicDeleteProcessor` 接口的内容如下: ```java public interface LogicDeleteProcessor { /** * 用户构建查询正常数据的条件 * @param logicColumn * @param dialect */ String buildLogicNormalCondition(String logicColumn, IDialect dialect); /** * 用户与构建删除数据时的内容 * @param logicColumn * @param dialect */ String buildLogicDeletedSet(String logicColumn, IDialect dialect); /** * 用于构建通过 QueryWrapper 查询数据时的内容 * @param queryWrapper * @param tableInfo */ void buildQueryCondition(QueryWrapper queryWrapper, TableInfo tableInfo); } ``` 具体实现可以参考:[DefaultLogicDeleteProcessor](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-core/src/main/java/com/mybatisflex/core/logicdelete/impl/DefaultLogicDeleteProcessor.java) ## SpringBoot 支持 在 SpringBoot 项目下,直接通过 `@Configuration` 即可使用: ```java @Configuration public class MyConfiguration { @Bean public LogicDeleteProcessor logicDeleteProcessor(){ return new DateTimeLogicDeleteProcessor(); } } ``` ## 全局配置逻辑删除字段 在 `MyBatis-Flex` 中,可以使用 `FlexGlobalConfig` 在 `MyBatis-Flex` 启动之前,指定项目中的逻辑删除列的列名。 ```java FlexGlobalConfig.getDefaultConfig().setLogicDeleteColumn("del_flag"); ``` 这样就可以省略实体类属性上的 `@Column(isLogicDelete = true)` 注解了。 ```java public class Account { // @Column(isLogicDelete = true) private Boolean delFlag; } ```