前两天在公司茶水间碰到老李,他一边冲咖啡一边吐槽说“你知道不,我们那个小程序上线出Bug了,居然是因为没填createTime字段…我真是气得脑瓜仁疼”。我说你们不是用的 SpringBoot 吗?他翻了个白眼,“是啊,用的是啊,但谁让我们一直手动填这些字段的,哪个不长眼的漏了就出事”。我当时一边喝豆浆一边心想,哎呀你们这是不懂自动填充的六种玩法啊,得亏我上个月刚整理了一遍,不然我也得跟你一样天天焦头烂额。
第一种:最基础的 @TableField 自动填充
这个其实很多人知道但还是不用,可能就图省事。你只要在字段上加个注解@TableField(fill = FieldFill.INSERT),比如说createTime,然后再配置一个MetaObjectHandler的实现类,就能在 insert 的时候自动把当前时间戳塞进去。比如:
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
然后你的MetaObjectHandler长这样:
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
就这点代码,省下的可能是几百次脑抽写错字段的机会。
第二种:同时搞定 insert 和 update 的 updatedTime
这个也特别常见,尤其是你那种表每次改都要记录updateTime的场景。我一般是这么写的:
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
MetaObjectHandler里就加一个updateFill:
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
我之前在电梯里还教过我们测试妹子怎么查更新记录,全靠这个。
第三种:用枚举搞定状态字段填充
这个之前被我们项目老大夸过“干得漂亮”,就是那种新建一条记录的时候,状态字段默认要是ENABLED,你就可以写成:
@TableField(fill = FieldFill.INSERT)
private StatusEnum status;
然后填充逻辑里:
this.strictInsertFill(metaObject, "status", StatusEnum.class, StatusEnum.ENABLED);
比你每次写entity.setStatus(StatusEnum.ENABLED)看起来就高级,而且更不容易出错。
第四种:ThreadLocal 方式搞当前登录人填充
这个就稍微复杂点,但是特别实用。像我之前做审批流系统的时候,每条记录要带上谁创建的、谁修改的。我们是这么搞的:
先用一个UserContextHolder去放 ThreadLocal:
public class UserContextHolder {
private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void set(User user) { userThreadLocal.set(user); }
public static User get() { return userThreadLocal.get(); }
public static void clear() { userThreadLocal.remove(); }
}
然后在拦截器里塞进去用户信息,MetaObjectHandler 里面就可以这么写:
this.strictInsertFill(metaObject, "createdBy", String.class, UserContextHolder.get().getUsername());
你要是用了 Sa-Token 或者 Spring Security,改一下获取逻辑就完事。
第五种:通过拦截器统一处理,脱离 MyBatis-Plus 也能玩
有次我们组要迁一个老项目,那时候没用 MyBatis-Plus,我就手搓了个Interceptor,专门拦住 insert 和 update 的语句,把那些字段强行加上默认值。虽然不推荐这么搞,但在没有 MetaObjectHandler 的年代,这就是救命稻草。
public class FillFieldInterceptor implements Interceptor {
// 简化版逻辑,实战中你得判断方法类型和目标字段
}
就像以前没空调的时候靠蒲扇过夏天,虽然土,但真管用。
第六种:数据库默认值兜底 + 显式字段忽略策略
这个真的是终极保险了,你不仅在 Java 层填了字段,数据库层也加上默认值,比如DEFAULT CURRENT_TIMESTAMP。再配合一下实体类字段写成:
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
而不是用transient或者@JsonIgnore这种让它不进数据库的,万一真漏了、或者有人手动写了个裸 SQL,不也还有数据库兜着吗?
说真的,每次看到别人还在手动塞createTime,我都想拍拍他肩膀说一句,“兄弟,春天早来了,你咋还自己种麦子呢”。这几个技巧放在一起用,开发体验真的舒服,字段那一坨烦人的屎山就瞬间少一半。
不过别光看,赶紧回去改你项目里的那些实体类吧,免得哪天改接口的时候又听到有人喊“卧槽,我忘填字段了”……