2022年最新版Mybatis-plus3.5.0全面攻略(二)自动填充和逻辑删除的实际方案

前言:现实开发中我们常常会遇到这样一种情况:技术经理要求开发人员创建的每一个表都要有一些公共字段:创建者id、更新者id、创建及更新时间、逻辑删除字段、租户字段等等。

而我们在新增记录或者更新记录的时候往往要频繁处理这些字段,而抽离出来的话,与持久层框架的结合又很不舒服。好在mybatis-plus为我们提供了很好用的自动填充逻辑删除支持。

思路

上次有朋友说他们平时对于持久层的实体类对象,会把公共字段抽出来当一个父类,而不是每一个实体类中都有这些字段,mybatis-plus支持了抽出父类的模式,也支持后面一种模式(笔者用的就是后面一种),因此本篇会两种都讲,主要以前者为主。

mybatis-plus实现自动填充和逻辑删除的逻辑大致是:

  1. 实体类中对需要自动填充和逻辑删除的字段做标记配置(交给公共类去实现或者交给代码生成器去实现)
  2. 配置全局的自动填充实现代码
  3. 配置全局的逻辑删除实现配置
  4. 业务代码不需要编写任何公共字段的逻辑

公共Entity方式

表字段

首先看一下数据库中涉及的公共字段,逻辑删除一般用tinyint或者boolean(如果支持的话)

image.png

  • 对于创建时间和创建人,要实现执行insert语句时自动添加创建信息
  • 对于更新时间和更新人,要实现执行update语句时自动添加更新信息(如果要创建时也添加也可以)
  • 创建数据时自动添加逻辑删除值为false
  • 删除数据时将记录的逻辑删除值设置为true
  • 所有查询操作以及删改操作的范围一律默认加上逻辑删除值为false

符合ActiveRecord模式的entity公共父类

上文讲过entity是支持ActiveRecord模式的,放在java项目中,简单来说就是可以直接通过实体来进行增删改查操作,即:

mapper.insert(entity)
↓↓↓↓↓↓
entity.insert()
复制代码

而如果我们在代码生成器中设置activeRecord模式为true,那么生成的entity类就会集成mybatis-plus的com.baomidou.mybatisplus.extension.activerecord.Model类。
那么如果我们要给所有的entity设置公共父类用以拓展(比如公共字段这些),就需要继承这个Model类。
即,mybatis-plus的Model为最上级,然后是中间的公共父类,最后是一个个的业务Entity类。
当然,要是不想使用activeRecord模式也就不需要继承Model了,直接一个简单的java类当公共父类即可。
下面是示例的公共entity类代码:

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @ClassName EntityCommon
 * @Description 公共entity类
 * @Author Chenyongyu
 * @Date 2022/1/12 15:19
 * @Version 1.0
 **/

@Getter
@Setter
public abstract class EntityCommon<T extends EntityCommon<T>> extends Model<T> {

    @TableField(value = "create_id", fill = FieldFill.INSERT)
    // 创建人id
    private Long createId;

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

    @TableField(value = "update_id", fill = FieldFill.UPDATE)
    // 更新人id
    private Long updateId;

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

    // 逻辑删除
    @TableLogic
    @TableField(value = "deleted")
    private boolean deleted;

    public abstract Serializable pkVal();
}
复制代码
  • Model类中的泛型T用来传递具体实体entity类,这样mybatis-plus才能给持久层传递此类对应的表信息等,因此泛型信息需要层层继承
  • 抽象方法pkVal,具体实体类需要实现此方法,mybatis-plus通过此方法找到实体的主键,比如区分插入还是更新等逻辑
  • @TableField + fill注解标记自动填充的字段,其中value为对应表字段,fill为填充模式,有INSERT、UPDATE、INSERT_UPDATE这几种模式。比如更新时间这个字段,在新增记录时,有的人习惯也加上更新时间,有的人习惯更新时间为空,这就是UPDATE和INSERT_UPDATE的区别。
  • @TableLogic标记逻辑删除字段

逻辑删除的配置

因为逻辑删除既支持boolean型,也支持字符串、数字型甚至是时间类型,所以我们需要配置删除状态和未删除状态对应的数据库值。这里mysql我们使用的数据库类型为tinyint(1),所以逻辑删除值可以用1与0表示。具体需要配置在application.yaml文件中:

mybatis-plus:
  #...省略其他配置
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
复制代码

这样配置的效果是:在新增记录时,默认会给deleted字段赋值0,涉及查询时(update等操作默认的where条件也算),会默认添加deleted=0的条件,即逻辑未删除的记录。执行delete语句时,不会删除记录,只会把记录对应的deleted属性设置为1。

代码生成器

最后我们要修改代码生成器中关于entity的策略配置。

  • 一方面,要设置entity的公共父类
  • 另一方,要设置entity生成忽略的字段,即我们分离出去的updateTime,deleted等。
.strategyConfig(builder -> {
    builder
            .addInclude("table_4_mp")
            // 跳过视图的生成
            .enableSkipView()
            .entityBuilder()
            // (重要)主键模式,这里设置自动模式,配合mysql的自增主键
            .idType(IdType.AUTO)
            // entity文件名,这里配置后面统一加Entity后缀
            .formatFileName("%sEntity")
            // activeRecord模式,使用上来说就是可以直接在entity对象上执行insert、update等操作
            .enableActiveRecord()
            // ###########修改点是下面两条##############
            // 公共父类
            .superClass(EntityCommon.class)
            // 忽略的列
            .addIgnoreColumns("create_time","create_id","update_time","update_id","deleted")
            .build();
})
复制代码

我们看一眼生成的类:

image.png

编写自动填充逻辑

这一步是实现mybatis-plus自动填充的关键一步,即自定义自动填充具体的业务实现

  1. 新建一个springbean继承MetaObjectHandler接口
  2. 实现insertFill和updateFill方法
  3. 编写每个字段具体的业务实现
@Component
public class AutoFillMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 创建时间,取当前时间,也可以自定义
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        // 创建人id,这个看自身业务如何获取,我这里是获取的保存在上下文(ThreadLocal)中的用户id
        this.strictInsertFill(metaObject, "createId", Long.class, UserContext.getUserInfo().get().getUserId());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 更新时间,取当前时间,也可以自定义
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        // 更新人id,这个看自身业务如何获取,我这里是获取的保存在上下文(ThreadLocal)中的用户id
        this.strictUpdateFill(metaObject, "updateId", Long.class, UserContext.getUserInfo().get().getUserId());
    }
}
复制代码

这里稍微说一下strictInsertFill方法和strictUpdateFill方法

MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal)
复制代码

四个参数分别是:

  • 透传的metaObject对象,即用来插入或修改的表实体类对象,可以在这里读取或修改
  • 字段名,是类字段名,不是表字段名
  • 字段类型,java类型
  • 填充的值,这个就是最关键的我们想要自动填充的业务值,比如当前时间、当前会话代表的用户id、当前所在环境等等

验证

到这里,自动填充和逻辑删除的设置就做好了,我们编写一些代码来测试一下:

// 模拟会话状态,真正业务环境会在调用controller接口时往会话中加入当前用户id
UserInfo userInfo = new UserInfo();
userInfo.setUserId(34112398L);
UserContext.getUserInfo().set(userInfo);
// 直接用new的实体来insert
Table4MpEntity table4MpEntity = new Table4MpEntity();
table4MpEntity.setName("辣椒儿");
table4MpEntity.setAge(28);
table4MpEntity.insert();
System.out.println("createTime:"+table4MpEntity.getCreateTime());
System.out.println("updateTime:"+table4MpEntity.getUpdateTime());
// 修改年龄
table4MpEntity.setAge(29);
table4MpEntity.updateById();
System.out.println("createTime:"+table4MpEntity.getCreateTime());
System.out.println("updateTime:"+table4MpEntity.getUpdateTime());
// 删除
table4MpEntity.deleteById();
// 再查询
Table4MpEntity table4MpEntitySearch = table4MpMapper.selectById(table4MpEntity.getId());
System.out.println(table4MpEntitySearch == null);
复制代码

结果:

createTime:2022-01-17T16:28:33.139
updateTime:null
createTime:2022-01-17T16:28:33.139
updateTime:2022-01-17T16:28:35.051
true
复制代码

image.png

自动填充和逻辑删除都是有效的。

独立entity方式

最后再介绍一下独立entity,即不适用公共的父类的方式,这种方式下createTime、updateId、deleted等在各自的entity类内部,包括各自的注解等。
这种情况下如果手动编写注解,则会面临下次执行代码生成器时自定义的代码被顶掉的问题。
好在mybatis-plus在代码生成器中为我们提供了相关设置:

.strategyConfig(builder -> {
    builder
            .addInclude("table_4_mp")
            // 跳过视图的生成
            .enableSkipView()
            .entityBuilder()
            // (重要)主键模式,这里设置自动模式,配合mysql的自增主键
            .idType(IdType.AUTO)
            // entity文件名,这里配置后面统一加Entity后缀
            .formatFileName("%sEntity")
            // activeRecord模式,使用上来说就是可以直接在entity对象上执行insert、update等操作
            .enableActiveRecord()
            // 添加tableField注解
            .enableTableFieldAnnotation()
            // 自动填充字段
            .addTableFills(Arrays.asList(
                    new Column("create_time", FieldFill.INSERT),
                    new Column("create_id", FieldFill.INSERT),
                    new Column("update_time", FieldFill.UPDATE),
                    new Column("update_id", FieldFill.UPDATE)
            ))
            // 逻辑删除字段
            .logicDeleteColumnName("deleted")
            .build();
})
复制代码

这里主要修改了enableTableFieldAnnotation、addTableFills、logicDeleteColumnName这三个设置,指定逻辑删除的字段和自动填充的字段。生成后的entity效果:

image.png

其他诸如自动填充业务逻辑的配置、逻辑删除的配置文件设置等都是一样的。

おすすめ

転載: juejin.im/post/7054093229930594317