MySQL把字段从 TIMESTAMP 改成 DATETIME,线上直接炸了!
事发:一个莫名其妙的空指针
周五下午,我正准备摸鱼等下班,测试同学找过来:库存保存接口报错了,你赶紧看看!
我打开日志一看,好家伙,一片红色:
java.sql.SQLException: Field 'create_time' doesn't have a default value
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException
嗯?我明明在数据库里给 create_time 设置了 DEFAULT CURRENT_TIMESTAMP,怎么会说没有默认值呢?
而且这段代码上线半年了,一直跑得好好的。仔细一看发版记录,这次只改了一个东西:把 create_time 的字段类型从 TIMESTAMP 改成了 DATETIME。
就这?就改了个字段类型就出问题了?
先看执行的语句,create_time和update_time都设置了默认值,理论上应该不会有问题才对?
## MySQL 8.0
ALTER TABLE t_inventory
MODIFY COLUMN create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY COLUMN update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
问题复现:诡异的行为差异
先来看看我的实体类:
@Data
@TableName("t_inventory")
public class Inventory {
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
再看看 MyBatis-Plus 打印出来的 SQL:
INSERT INTO t_inventory
( ..., create_time, update_time )
VALUES ( ..., ?, ? )
看起来没毛病啊?@TableField(fill = FieldFill.INSERT) 配合数据库 DEFAULT CURRENT_TIMESTAMP,这不是标准用法吗?
但问题就出在这里! 字段有了,值呢? 日志里的参数显示:create_time 的值是 null。
这就是报错的直接原因:INSERT 语句显式地插入了 null,而 MySQL 的 DEFAULT 约束只在 INSERT 语句不包含该列时才生效。
深入:代码里埋的"雷"
但这就引发了一个更深的疑问:既然 create_time 是 null,为什么之前用 TIMESTAMP 的时候没报错?
为了搞清楚这个问题,我做了一个对比实验:
| 字段类型 | INSERT 是否包含该字段 | 传入的值 | MySQL 的行为 | 结果 |
|---|---|---|---|---|
| TIMESTAMP | 是 | NULL | 自动转换为 CURRENT_TIMESTAMP |
✅ 成功 |
| DATETIME | 是 | NULL | 严格模式下拒绝插入 NULL | ❌ 报错 |
TIMESTAMP 会"好心"地把 NULL 转成当前时间,这是 MySQL 的历史行为,算是 TIMESTAMP 类型的一个"隐藏特性"。
而 DATETIME 不会做这种隐式转换,尤其是在 MySQL 8.0 默认开启的严格模式(STRICT_TRANS_TABLES)下,NOT NULL 字段插入 NULL 直接报错。
所以真相是:
- 以前用 TIMESTAMP:MySQL 默默帮我们把
null转成了当前时间 → 掩盖了问题 - 现在用 DATETIME:MySQL 不装了,直接报错 → 问题暴露
但新的问题又来了:为什么 create_time 会是 null?fill = FieldFill.INSERT 不是应该自动填充吗?
带着这个疑问,我又翻了一遍项目代码。
真相:一个经典的"半成品"用法
我搜遍了整个项目,愣是没找到一个 MetaObjectHandler 的实现类。
这就是问题所在:
- 实体类写了
fill = FieldFill.INSERT→ 看起来像是"我要自动填充" - 但没人实现
MetaObjectHandler→ 实际上根本没人在填充 - MyBatis-Plus 只能拿到一个
null值 → 原封不动地塞进了 SQL - 以前 TIMESTAMP 帮忙擦了屁股 → 岁月静好
- 现在 DATETIME 不惯着了 → 直接报错
FieldFill.INSERT 只是一个"声明",真正的填充逻辑在 MetaObjectHandler 里。写了声明却没有实现,这就像是你买了个高级咖啡机,天天按"拿铁"按钮,结果出来的永远是白开水——因为你忘了放咖啡豆啊!
不要问我为什么没实现 MetaObjectHandler,我也不知道,代码来的时候就这样。(反正写这段代码的人已经离职了,这个锅我不背。)
为什么数据库的 DEFAULT 没生效?
这里再补充一个关键知识点:
MySQL 的 DEFAULT 约束只在 INSERT 语句没有显式指定该列时才生效。
-- ✅ 没有指定 create_time,DEFAULT 生效
INSERT INTO t_inventory (date, area) VALUES ('2026-09-02', '上海');
-- create_time 自动变成 CURRENT_TIMESTAMP
-- ❌ 指定了 create_time,传入 NULL,DEFAULT 不生效
INSERT INTO t_inventory (date, area, create_time)
VALUES ('2026-09-02', '上海', NULL);
-- 报错:Column 'create_time' cannot be null
因为 fill = FieldFill.INSERT 的存在,MyBatis-Plus 生成的 INSERT 语句包含了 create_time 列,所以数据库的 DEFAULT CURRENT_TIMESTAMP 根本不会被触发。
解决方案:三条路任选
方案一:去掉 fill 注解(直接快速)
既然没打算实现 MetaObjectHandler,那这个 fill 注解就是掩耳盗铃,不如删掉:
// 去掉 fill,让数据库的 DEFAULT 来做它该做的事
@TableField("create_time")
private LocalDateTime createTime;
@TableField("update_time")
private LocalDateTime updateTime;
方案二:把 MetaObjectHandler 补上(最规范的)
如果确实需要 Java 层统一管理时间:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
方案三:手动 set 时间(紧急止血)
线上炸了,来不及改代码?先这样顶着:
entity.setCreateTime(LocalDateTime.now());
entity.setUpdateTime(LocalDateTime.now());
总结:这次踩坑教会我的事
@TableField(fill = FieldFill.INSERT)只是一个声明,真正的填充逻辑在MetaObjectHandler里,两者要配套使用- MySQL 的 DEFAULT 只在 INSERT 不包含该列时才生效,一旦包含了该列,哪怕传的是
null,DEFAULT 也不会触发 - TIMESTAMP 会把 NULL 转成当前时间,这个"贴心"的行为可能掩盖了很多问题
- DATETIME 在 MySQL 8.0 严格模式下不会容忍 NULL,这其实更符合规范,帮我们暴露了隐藏的问题
- 字段类型改动要谨慎,看起来只是改了个类型,背后的行为差异可能超乎想象
最后送大家一句话:代码里写了 fill 就记得实现 MetaObjectHandler,不然就是在给未来的自己埋雷。

浙公网安备 33010602011771号