背景
在项目中有一个表存了一个日期字段: payroll_cycle_end_day date NOT NULL COMMENT ‘计薪周期结束日期’, java里的entity字段为日期 date 格式, 在设置的时候使用了cn.hutool.core.date.DateUtil.endOfMonth(payrollCycleBeginDay) 获取月末时间 后面很自然的程序执行save操作就朝mysql里插入了
结果,本地测试没问题, 测试环境也没异常,线上数据库里本来是十月的数据; 开始时间存在的是2025-10-01没问题, 结束时间就存成了 2025-11-01, 而不是预期的 2025-10-31
经过几轮排查定位,发现是mysql的版本不一致导致的,本地和测试连的同一个库,版本是:mysql5.5; 线上用的是:mysql5.7
SQL测试(Mysql5.7)
字段:
`attendance_cycle_end_day` date NOT NULL COMMENT '考勤周期结束日期',
测试case:
update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59" where id = 1094799
### 结果:2025-10-31
update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59.9999" where id = 1094799
### 结果:2025-11-01
### 进一步测试
update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59.4" where id = 1094799
### 结果:2025-10-31
update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59.5" where id = 1094799
### 结果:2025-11-01
测试结论
在mysql5.7版本时,date类型的字段当存入数据带毫秒的时候,小数点后的数会遵守四舍五入原则,可能发生时间进位
解决办法
1、再entity设置的时候,如果mysql字段是date类型可以粗暴点直接取当天的开始时间, 比如:DateUtil.beginOfDay(DateUtil.endOfMonth(payrollCycleBeginDay));
2、entity的字段上加注解 @Temporal(TemporalType.DATE)
使用场景对比
@Entity
public class Employee {
// 只关心日期,比如生日、入职日期
@Temporal(TemporalType.DATE)
private Date hireDate; // 存储为:2025-10-31
// 只关心时间,比如打卡时间
@Temporal(TemporalType.TIME)
private Date punchTime; // 存储为:14:30:00
// 关心完整的日期时间,比如创建时间、更新时间
@Temporal(TemporalType.TIMESTAMP)
private Date createTime; // 存储为:2025-10-31 14:30:00.123
}
如果想要 年-月-日 时:分:秒 舍弃毫秒, 得java特殊处理下
扩展到DATETIME/TIMESTAMP类型
依然会发生进位问题
执行:
### 字段设置:
`update_time` datetime NOT NULL COMMENT '更新时间',
### sql语句:
update payroll_cycle_attendance_snapshot set update_time='2025-10-31 17:30:56.9999' where id = 1094799
### 结果: 2025-10-31 17:30:57
总结
这种坑很恶心, 不容易察觉,如果是日期多了一天可能导致大问题,如果是秒数进位,不容易发现,而且有的进位有的不进位, 如果没想到这块,复现结果莫名其妙如抽风
所以使用java朝mysq数据库里插入时间的时候,在逻辑许可的情况下,最好能特殊处理下,把毫秒去掉
或者直接就用int存时间戳, 或者用字符串存储(php就习惯这么处理); 这样也能规避一下问题