MyBatis-Flex 逻辑删除的相关用法

时间:2024-12-12 09:11:38

如果想了解MyBatis-Flex相关知识,可以移步到我的另一篇博客MyBatis-Flex BaseMapper的接口基本用法-****博客

目录

一、@Column

二、逻辑删除相关场景

通过@Column设置字段逻辑删除

在逻辑删除的基础上,自动更新更新时间update_time

跳过逻辑删除处理


一、@Column

MyBatis-Flex 提供了 @Column 用来对字段进行更多的配置,以下是 @Column 的代码定义:

public @interface Column {

    /**
     * 字段名称
     */
    String value() default "";

    /**
     * 是否忽略该字段,可能只是业务字段,而非数据库对应字段
     */
    boolean ignore() default false;

    /**
     * insert 的时候默认值,这个值会直接被拼接到 sql 而不通过参数设置
     */
    String onInsertValue() default "";

    /**
     * update 的时候自动赋值,这个值会直接被拼接到 sql 而不通过参数设置
     */
    String onUpdateValue() default "";

    /**
     * 是否是大字段,大字段 APT 不会生成到 DEFAULT_COLUMNS 里
     */
    boolean isLarge() default false;

    /**
     * 是否是逻辑删除字段,一张表中只能存在 1 一个逻辑删除字段
     * 逻辑删除的字段,被删除时,会设置为 1,正常状态为 0
     */
    boolean isLogicDelete() default false;

    /**
     * 是否为乐观锁字段,若是乐观锁字段的话,数据更新的时候会去检测当前版本号,若更新成功的话会设置当前版本号 +1
     * 只能用于数值的字段
     */
    boolean version() default false;

    /**
     * 配置的 jdbcType
     */
    JdbcType jdbcType() default JdbcType.UNDEFINED;

    /**
     * 自定义 TypeHandler
     */
    Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;

}

二、逻辑删除相关场景

通过@Column设置字段逻辑删除

逻辑删除指的是在删除数据的时候,并非真正的去删除,而是将表中列所对应的状态字段(status)做修改操作, 实际上并未删除目标数据。

我们可以进行表的字段设计时,用一个列标识该数据的 "删除状态",在 mybatis-flex 中,正常状态的值为 0, 已删除 的值为 1(可以通过设置 FlexGlobalConfig 来修改这个值)。

@Column(isLogicDelete = true)
private int isDeleted;

/*
对应数据库的表结构中为:
is_deleted tinyint(1) default 0 numm comment '是否已逻辑删除 0-否 1-是';
*/

当 "tb_account" 的数据被删除时( is_delete = 1 时),我们通过 MyBatis-Flex 的 selectOneById 去查找数据时,会查询不到数据。 原因是 selectOneById 会自动添加上 is_delete = 0 条件,执行的 sql 如下:

SELECT * FROM tb_account where id = ? and is_delete = 0

不仅仅是 selectOneById 方法会添加 is_delete = 0 条件,BaseMapper 的以下方法也都会添加该条件:

  • selectOneBy**
  • selectListBy**
  • selectCountBy**
  • paginate

同时,比如 Left Join 或者子查询等,若 子表也设置了逻辑删除字段, 那么子表也会添加相应的逻辑删除条件,例如:

QueryWrapper query1 = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(ARTICLE).as("a").on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
    .where(ACCOUNT.AGE.ge(10));
/*
其执行的 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。

在逻辑删除的基础上,自动更新更新时间update_time

(1)

数据填充指的是,当 Entity 数据被插入或者更新的时候,会为字段进行一些默认的数据设置。这个非常有用,比如当某个 entity 被插入时候 会设置一些数据插入的时间、数据插入的用户 id,多租户的场景下设置当前租户信息等等。

MyBatis-Flex 提供了两种方式,帮助开发者进行数据填充。

  • 通过 @Table 注解的 onInsert  和 onUpdate配置进行操作。 
  • 通过 @Column  注解的 onInsertValue  和 onUpdateValue配置进行操作。 

@Table的注解和@Column注解的填充有什么区别?

  • @Table 注解的 onInsert 主要是在 Java 应用层面进行数据设置。 
  • @Column 注解的 onInsertValue 则是在数据库层面进行数据设置。

首先,需要在后端项目的model实体类中,通过注解配置将该类的对象属性createTime和updateTime,映射到数据库表中的特定字段create_time和update_time的默认值设置等信息。

@Column(onInsertValue = "now()")
private Date createTime;

@Column(onUpdateValue = "now()", onInsertValue = "now()")
private Date updateTime;

@Column(isLogicDelete = true)
private int isDeleted;

/*
对应数据库的表结构中为:
create_time timestamp null comment '创建时间';
update_time timestamp null comment '更新时间';
is_deleted tinyint(1) default 0 numm comment '是否已逻辑删除 0-否 1-是';
*/

其中,onInsertValue表示当数据被更新时,设置的默认值。onInsertValue配置的内容 "now()" 会直接参与 SQL 的赋值拼接。而onUpdateValue表示当数据被更新时,设置的默认值。onUpdateValue配置的内容 "now()" 会直接参与 SQL 的赋值拼接。

在 insert 中,onInsertValue 配置的内容会直接参与 SQL 拼接,而不是通过 JDBC 的 Statement 参数设置,需要开发者注意 onInsertValue 的内容,否则可能会造成 SQL 错误。

(2)

UpdateChain 是一个对 UpdateEntity、UpdateWrapper 等进行封装的一个工具类,方便用户用于进行链式操作。

假设我们要更新 Account 的 userName 为 "张三",更新年龄在之前的基础上加 1,更新代码如下:

@Test
public void testUpdateChain() {
    UpdateChain.of(Account.class)
        .set(Account::getUserName, "张三")
        .setRaw(Account::getAge, "age + 1")
        .where(Account::getId).eq(1)
        .update();
}

/*
以上方法调用时,MyBatis-Flex 内部执行的 SQL 如下:
sql
UPDATE `tb_account` SET `user_name` = '张三' , `age` = age + 1
WHERE `id` = 1
*/

经过测试之后,发现dataMapper.deleteBatchById(idsList)似乎不会自动更新时间update_time,不知道为什么。不是说当调用 deleteBatchById()方法时,MyBatis-Flex 会根据实体类信息和注解配置,动态生成一条更新语句,而不是删除语句吗??按道理来说,更新语句不应该会触发更新时间update_time字段的自动更新吗?(没搞明白。。)

因此,可以通过MyBatis-Flex 提供的链式操作方式,来替换掉基础的deletedById()方法。 

// dataMapper.deleteBatchByIds(idsList);
UpdateChain.of(dataMapper)
	.set(DataModel::getIsDeleted, 1)
	.where(DataModel::getIsDeleted, 0)
	.and(DataModel::getId).in(idsList)
	.update();

这样MyBatis-Flex框架就会走更新语句的流程,数据库表中就可以完成自动更新update_time更新时间字段了。

跳过逻辑删除处理

在某些场景下,比如说需要构建回收站功能,那我们在执行查询、更新或删除数据时,有必要跳过 MyBatis-Flex 自动添加的逻辑删除的相关条件。

此时,我们可以使用 LogicDeleteManager.execWithoutLogicDelete() 方法处理。这种方法在需要对所有数据进行操作时非常有用,比如批量导出数据、进行数据恢复等场景。

LogicDeleteManager 是一个用于处理逻辑删除的管理器。execWithoutLogicDelete() 方法的作用是在执行某些操作时忽略逻辑删除的规则。这意味着,当使用这个方法执行查询、插入、更新或删除操作时,系统不会考虑逻辑删除标志,即会处理所有数据,包括那些被标记为已删除的数据。

比如,

LogicDeleteManager.execWithoutLogicDelete(()->
        accountMapper.deleteById(1)
        );

以上代码中,accountMapper 会直接对 Account 数据进行物理删除,忽略逻辑删除字段配置。

代码如下:

LogicDeleteManager.execWithoutLogicDelete(()->{
	// 此处写逻辑
	UpdateChain.of(dataMapper)
	.set(DataModel::getIsDeleted, 0)
	.where(DataModel::getIsDeleted, 1)
	.and(DataModel::getId).in(idsList)
	.update();
	});

上述代码中,UpdateChain.of(dataMapper) 创建了一个数据映射器对象dataMapper的更新链对象。.set(DataModel::getIsDeleted, 0) 设置 DataModel 的 isDeleted 字段值为 0,表示未删除状态。.where(DataModel::getIsDeleted, 1) 指定更新条件之一是isDeleted 字段值为 1,即逻辑上已被删除的数据。.and(DataModel::getId).in(idsList) 添加额外的条件:id 字段的值必须在 idsList 列表中。.update() 执行更新操作。

这段代码的目的是在逻辑删除被忽略的情况下,将指定 id 的数据从逻辑删除状态恢复到未删除状态。具体来说,只有那些 isDeleted 字段值为 1 并且 id 在 idsList 中的数据才会被更新。更新后,这些数据的 isDeleted 字段值会被设置为 0,表示这些数据不再被视为已删除。