前言:现实开发中我们常常会遇到这样一种情况:技术经理要求开发人员创建的每一个表都要有一些公共字段:创建者id、更新者id、创建及更新时间、逻辑删除字段、租户字段
等等。
而我们在新增记录或者更新记录的时候往往要频繁处理这些字段,而抽离出来的话,与持久层框架的结合又很不舒服。好在mybatis-plus为我们提供了很好用的自动填充和逻辑删除支持。
思路
上次有朋友说他们平时对于持久层的实体类对象,会把公共字段抽出来当一个父类,而不是每一个实体类中都有这些字段,mybatis-plus支持了抽出父类的模式,也支持后面一种模式(笔者用的就是后面一种),因此本篇会两种都讲,主要以前者为主。
mybatis-plus实现自动填充和逻辑删除的逻辑大致是:
- 实体类中对需要自动填充和逻辑删除的字段做标记配置(交给公共类去实现或者交给代码生成器去实现)
- 配置全局的自动填充实现代码
- 配置全局的逻辑删除实现配置
- 业务代码不需要编写任何公共字段的逻辑
公共Entity方式
表字段
首先看一下数据库中涉及的公共字段,逻辑删除一般用tinyint或者boolean(如果支持的话)
- 对于创建时间和创建人,要实现执行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();
})
复制代码
我们看一眼生成的类:
编写自动填充逻辑
这一步是实现mybatis-plus自动填充的关键一步,即自定义自动填充具体的业务实现。
- 新建一个springbean继承MetaObjectHandler接口
- 实现insertFill和updateFill方法
- 编写每个字段具体的业务实现
@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
复制代码
自动填充和逻辑删除都是有效的。
独立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效果:
其他诸如自动填充业务逻辑的配置、逻辑删除的配置文件设置等都是一样的。