记一次时间类型格式化问题:Hutools DbUtil 拼接 SQL 时如何避免日期精度丢失?
一、问题背景
在一次上游服务 SDK 开发中,我们需要通过 Hutools 工具包中的 DbUtil 实现数据持久化。在拼接 SQL 更新语句时,遇到了一个隐蔽的日期格式化问题。核心代码如下:
java
Entity.create("table_name")
.set("start_time", "<= " + xxx.getDate()) // 直接拼接日期对象
.set("end_time", "IS NULL"));
其中 xxx.getDate()
对应实体类的字段定义如下:
java
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8")
private Date date; // 期望保留毫秒精度
问题现象 :
数据库记录中的 start_time
字段丢失了毫秒(SSS)精度,导致时间范围查询出现误差。
二、问题分析
1. 为什么会出现精度丢失?
- 直接字符串拼接 :
xxx.getDate()
返回java.util.Date
对象,调用toString()
方法时默认格式为EEE MMM dd HH:mm:ss zzz yyyy
,不包含毫秒信息。 - 注解作用范围 :
@JsonFormat
仅在 Jackson 序列化/反序列化 JSON 时生效,不影响Date.toString()
的行为。
2. 错误示例分析
java
// 假设 date 值为 2023-10-01 12:34:56.789
String sql = "<= " + date.toString();
// 实际拼接结果:<= Sat Oct 01 12:34:56 CST 2023
三、解决方案
1. 手动格式化日期(基础版)
通过 SimpleDateFormat
指定包含毫秒的格式:
java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String formattedDate = sdf.format(xxx.getDate());
Entity.create("table_name")
.set("start_time", "<= '" + formattedDate + "'") // 注意添加引号
2. 使用 Hutools 工具类(推荐)
利用 DateUtil.format()
简化操作,避免重复造轮子:
java
import cn.hutool.core.date.DateUtil;
String formattedDate = DateUtil.format(xxx.getDate(), "yyyy-MM-dd HH:mm:ss.SSS");
Entity.create("table_name")
.set("start_time", "<= '" + formattedDate + "'")
四、避坑指南
1. SQL 注入风险
直接拼接字符串存在安全隐患,建议改用 预编译占位符:
java
// 使用 Hutools 的预编译方式
Entity entity = Entity.create("table_name")
.set("start_time", new Condition("<= ?", formattedDate));
2. 时区一致性
确保数据库连接时区与 @JsonFormat
的 GMT+8
一致,避免时差问题。
五、总结
关键点 | 说明 |
---|---|
Date.toString() | 默认不包含毫秒,不可依赖其生成 SQL 字符串 |
@JsonFormat 局限 | 仅作用于 JSON 序列化,不影响其他场景的日期格式 |
手动格式化 | 使用 SimpleDateFormat 或 Hutools 工具类指定明确格式 |
安全拼接 | 优先使用预编译语句,避免拼接字符串带来的 SQL 注入风险 |
最佳实践:
- 始终通过 显式格式化 控制日期字符串的输出。
- 涉及时间比较时,确保数据库字段类型与精度(如
DATETIME(3)
)和代码格式一致。