目录

MyBatis-Plus 实现自动填充和逻辑删除

Spring Boot & MyBatis-Plus 实现自动填充和逻辑删除

业务需求

前台显示创建人、创建时间、修改人、修改时间,并记录删除人和删除时间。为了方便数据恢复和保护数据本身价值,希望删除后能找回记录。

相关原理

自动填充原理

MyBatis-Plus 文档对自动填充功能的介绍

关键是在实体类需要自动填充的字段中声明 @TableField 注解的 fill 属性,以及实现元对象处理器接口 MetaObjectHandler

注意
  • 填充原理是直接给 entity 的属性设置值
  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是 null
  • MetaObjectHandler 提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为 null 则不填充
  • 字段必须声明 TableField 注解,属性 fill 选择对应策略(见附录),该声明告知 Mybatis-Plus 需要预留注入 SQL 字段
  • 填充处理器 MyMetaObjectHandler 在 Spring Boot 中需要声明 @Component@Bean 注入

附录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
  * 格式如:fill = FieldFill.INSERT
  */
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入填充字段
     */
    INSERT,
    /**
     * 更新填充字段
     */
    UPDATE,
    /**
     * 插入和更新填充字段
     */
    INSERT_UPDATE
}   

逻辑删除原理

MyBatis-Plus 文档对逻辑删除的介绍

简而言之,逻辑删除就是将 delete 替换为 update 操作。

注意

只对自动注入的 SQL 起效(不支持自定义 SQL):

  • 插入: 不作限制
  • 查找:追加 where 条件过滤掉已删除数据,且使用条件构造器 wrapper.entity 生成的 where 条件会忽略该字段
  • 更新:追加where条件防止更新到已删除数据,且使用条件构造器 wrapper.entity 生成的 where 条件会忽略该字段
  • 删除:转变为 更新

字段类型支持说明:

  • 支持所有数据类型(推荐使用 Integer , Boolean , LocalDateTime
  • 如果数据库字段使用 datetime,逻辑未删除值和已删除值支持配置为字符串 null,另一个值支持配置为函数来获取值如 now()

具体实现

步骤一:数据库设计

向相关表中添加以下字段:

字段 类型 注释
create_user bigint 创建人id
create_time datetime 创建时间
update_user bigint 修改人id
update_time datetime 修改时间
delete_user bigint 删除人id
delete_time datetime 删除时间
is_deleted bit(1) 是否删除(默认值0)

步骤二:逻辑删除配置

在 Spring Boot 配置文件 application.yml 或者 application.properties 中配置逻辑删除配置。

application.yml 为例:

1
2
3
4
5
6
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag      # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1        # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

以上操作与 @TableLogic(value = "0", delval = "1") 添加注解属性同理,未删除状态值 value 默认为 0,已删除状态值 delval 默认为1。

步骤三:修改实体类

在需要实现自动填充和逻辑删除的相关实体类中添加以下字段及注解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * 创建人
 */
@TableField(value = "create_user", fill = FieldFill.INSERT)
private Long createUser;	

/**
 * 创建时间
 */
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;

/**
 * 修改人
 */
@TableField(value = "update_user", fill = FieldFill.UPDATE)
private Long updateUser;

/**
 * 修改时间
 */
@TableField(value = "update_time", fill = FieldFill.UPDATE)
private LocalDateTime updateTime;

/**
 * 删除人
 */
@TableField("delete_user")
private Long deleteUser;

/**
 * 删除时间
 */
@TableField("delete_time")
private LocalDateTime deleteTime;    

/**
 * 是否删除
 */
@TableLogic
@TableField("is_deleted")
private Boolean isDeleted;

以上代码中有几点需要注意:

  • 由于 创建人创建时间 是添加(插入)操作时自动填充,所以 fill 属性为 FieldFill.INSERT
  • 同理,修改人修改时间 是在修改操作时自动填充,所以 fill 属性为 FieldFill.UPDATE
  • MyBatis-Plus 官方并未提供删除操作的自动填充方法,所以不需要在 删除人删除时间 字段定义 fill 属性。不能用 FieldFill.UPDATE 或其他属性代替,否则在修改操作的时候也会更新该字段的值(具体实现见下文)
  • 在需要标识为”是否删除”的字段加上 @TableLogic 注解,如果数据库中没有定义默认值,可以在此处设置 isDeleted = false

步骤四:实现自动填充

自定义一个类 CustomMetaObjectHandler 实现 MetaObjectHandler 接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
public class CustomMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 创建人填充
        this.strictInsertFill(metaObject, "createUser", Long.class, getUserId());
        // 创建时间填充
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 修改人填充
        this.strictUpdateFill(metaObject, "updateUser", Long.class, getUserId());
        // 修改时间填充
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
    
    /**
      * 获取用户id(以 Guns 框架为例)
      */
    protected Long getUserId() {
        try {
           return LoginContextHolder.getContext().getUser().getId();
        } catch (Exception e) {
        	// 如果获取不到当前用户就存空id
           return -100L;
        }
    }
}

注:以上写法在 MyBatis-Plus 3.3.0 以上版本 有效。

此时还剩余 删除人删除时间 没有设置自动填充,由于 MyBatis-Plus 官方没有给出具体实现,可以使用下述方法:

Service 类的删除实现方法中,分别设置删除人 id 和删除时间,并作 updateById 操作,可以同时实现自动填充和逻辑删除。

1
2
3
4
5
6
7
8
@Override
public void delete(Entity entity) {
    // 设置删除人
    entity.setDeleteUser(getUserId());
    // 设置删除时间
    entity.setDeleteTime(LocalDateTime.now());
    updateById(entity);
}

总结

MyBatis-Plus 自带的接口和配置让自动填充和逻辑删除的实现更加简单,仅需添加 @TableField 注解的 fill 属性和添加 @TableLogic 注解即可实现这两个功能,通过实现 MetaObjectHandler 接口可个性化地定义填充内容和逻辑。

另外需要说明:

  • 创建人、修改人、删除人字段可以用字符串代替,显示更直观,减少后端根据用户 id 查询用户名的压力,但为了区分可能出现的同名情况,保留 id 更为合适(需要根据实际衡量)
  • MyBatis-Plus 3.3.0 以下的版本可以使用 setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) 方法设置填充值