☰
全字段 INSERT IGNORE:大约 70 行零日期,把证照列表加载打崩
2026/10/24 4:14:38 网站建设 项目流程

问题现象

早上业务那边反馈:正式环境管理系统里,点开“供应商证照列表”,页面转一圈直接报错白屏。

我本地复现不了,切到正式服看日志,满屏都是java.sql.SQLException: Zero date value prohibited。这个异常我问了一下元宝——大概就是 JDBC 读到了0000-00-00 00:00:00这种非法时间。

于是我赶紧SELECT了一下证照表,果然:大概 70 行cdatetime全是0000-00-00 00:00:00。

当时第一反应是“不可能吧?”——这列明明建表时写了DEFAULT CURRENT_TIMESTAMP,没传值也应该落当前时间才对。

下面就把这批零日期怎么混进去的、以及写入 / 读取 / 存量三处分别怎么补坑,完整记一下。

排查思路

先确认异常来源:Zero date value prohibited不是业务抛的,是 MySQL 驱动在把0000-00-00转成 JavaLocalDateTime时直接炸的。所以问题拆成两块:

  1. 这 70 行零日期是谁写的?
  2. 为什么DEFAULT CURRENT_TIMESTAMP没生效?

顺着列表加载的 DAO 反向查写入链路,最后卡在一个自己封装的insertIgnoreBatch方法上。它是全字段INSERT IGNORE,sql_mode又是严格模式。看到这里基本就有数了——默认值没兜底,null 被静默收成了全零。

根本原因

怎么理解:全字段INSERT IGNORE把 Java 的null收成了零日期,DEFAULT没有兜底?

其实是这样的:
insertIgnoreBatchAllColumn以及任意整列批量插入,会把实体的所有列打进 SQL。Java 侧LocalDateTime为 null,SQL 里就是NULL。业务代码没 set 时间,在这条路径上并不等于走库默认值!!!

DEFAULT只在 INSERT 不写该列,或该列写成DEFAULT关键字时生效。语句里已经是col = NULL,或写了一个非法日期,默认值不会补上。

INSERT IGNORE把NULL和非法 datetime 收成0000-00-00 00:00:00。按环境判断应该是严格模式。之后用 JDBCLocalDateTime去读,默认zeroDateTimeBehavior=EXCEPTION,就会抛零日期异常。外面常包着TransientDataAccessResourceException、LocalDateTimeTypeHandler、DataReadException。

AllColumn 把时间列写成 NULL

INSERT IGNORE 收成 0000-00-00

列表加载时 LocalDateTime 抛 Zero date value prohibited

解决方案

知道病根就好动手了,但动手顺序有讲究——先让页面能打开,再堵写入,最后清存量,别一上来就刷数据,入口没堵住,只会导致重复刷数据

第一:先让列表可访问
库里已经躺了 70 行零日期,列表一加载就炸,测试都没法验后面的改动。所以先在cdatetime、mdatetime两个字段上挂自定义TypeHandler,把0000-00-00在读取时转成null。注意是只挂这两个字段,不全局注册,免得波及其它表。

或者说,更简单的方法,可以在SQL返回时,如果判断出是全零的日期,则返回null。Java代码另外判断:

CASE WHEN CAST(cdatetime AS CHAR) LIKE '0000%' THEN NULL ELSE cdatetime END AS cdatetime

用CAST(col AS CHAR) LIKE '0000%'是因为严格 SQL 模式下直接拿datetime = '0000-00-00'做比较,有时候会被优化器或驱动提前拦掉,CAST 成字符串最稳。

方法有很多,这里我用了SQL的方法;

第二步:堵住写入
insertIgnoreBatch是全字段插入,Java 侧LocalDateTime是 null,SQL 里就是NULL,DEFAULT CURRENT_TIMESTAMP根本不会触发。所以在调批量插入之前,显式设时间:

LocalDateTimenow=LocalDateTime.now();row.setCdatetime(now);row.setMdatetime(now);

两张表都改:供应商证照图片审核子表,以及另一套库里的同构表。

第三步:刷存量

跟产品对了一下,这 70 行是历史脏数据,没有业务含义,统一刷成当前时间。查零日期别用= '0000-00-00',严格模式下不一定查得出来,用CAST(cdatetime AS CHAR) LIKE '0000%'最稳:

SELECTCOUNT(*)FROMyour_tableWHERECAST(cdatetimeASCHAR)LIKE'0000%'ORCAST(mdatetimeASCHAR)LIKE'0000%';

避免再次踩坑

这次排障最憋屈的点在于:每一层单独看都没问题,叠在一起就炸。

评审时扫这四件事。Skill 没挂上,或者在改老代码、手写 SQL 时,也看这四条:

  1. 是不是用了*BatchAllColumn或手写全列INSERT IGNORE?这种写法会把 null 打进 SQL,DEFAULT不会生效。
  2. 时间字段会不会以 null 进入 SQL?写进语句的NULL不是「没传值」,库不会补默认值。
  3. 实体或 Service 有没有在插入前赋值?建表语句有默认值,挡不住全字段插入。
  4. 读路径是不是用LocalDateTime或LocalDate映射datetime?是的话,零日期在驱动层就会抛。

团队用 Cursor 写代码时,把下面几条写进.cursorrules或 Skill。生成时就能拦住,不必等到评审。

## 数据库实体与写入约束 1. 带 `cdatetime`、`mdatetime`、`create_time`、`update_time` 的实体,创建对象时必须显式赋值,禁止把 null 交给 DAO: LocalDateTime now = LocalDateTime.now(); entity.setCdatetime(now); entity.setMdatetime(now); 2. 使用全字段 `INSERT IGNORE`(如 `insertIgnoreBatchAllColumn`、手写全列 insert ignore)时,时间字段必须非 null,插入前要生成赋值代码。 3. 读路径用 `LocalDateTime` 或 `LocalDate` 映射 `datetime` 时,要处理零日期 `0000-00-00`。挂在出问题的字段上,不要全局注册。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询