java.sql.Date 不包含 时间信息(时、分、秒、毫秒),它只表示一个日期(年、月、日)。

当你尝试将一个包含时间信息的 java.util.Date 或 java.time.LocalDate 转换为 java.sql.Date 时,所有的时间部分都会被丢弃。
详细解释
java.sql.Date 的设计初衷
java.sql.Date 是 JDBC API 的一部分,它的设计目的是为了与 SQL 标准中的 DATE 类型进行映射。
- SQL
DATE类型: 在 SQL 标准中,DATE类型只存储日期,不存储时间,在数据库中DATE '2025-10-27'只代表 2025 年 10 月 27 日这一天。 - JDBC 映射: 为了与数据库的
DATE类型对应,java.sql.Date被设计为只持有日期信息,它继承自java.util.Date,但通过重写toString()等方法,使其只显示日期部分。
为什么时间信息会丢失?
java.sql.Date 的构造函数和 valueOf() 方法会明确地将时间部分归零。
示例代码:

import java.sql.Date;
import java.util.Date as UtilDate; // 为了避免冲突,给 java.util.Date 起个别名
public class SqlDateExample {
public static void main(String[] args) {
// 1. 创建一个包含完整时间信息的 java.util.Date
// 假设当前时间是 2025年10月27日 15:30:45.123
UtilDate utilDate = new UtilDate();
System.out.println("原始 java.util.Date: " + utilDate);
// 输出类似: Fri Oct 27 15:30:45 CST 2025
// 2. 将 java.util.Date 转换为 java.sql.Date
// 关键点:这里会丢失时间信息!
sqlDate sqlDate = new java.sql.Date(utilDate.getTime());
System.out.println("转换后的 java.sql.Date: " + sqlDate);
// 输出类似: 2025-10-27
// 注意:时间部分 15:30:45.123 完全不见了
// 3. 使用 valueOf 方法 (参数是 java.sql.Date 的内部表示格式)
java.sql.Date sqlDateFromValueOf = java.sql.Date.valueOf("2025-10-27");
System.out.println("通过 valueOf 创建的 java.sql.Date: " + sqlDateFromValueOf);
// 输出: 2025-10-27
}
}
从上面的例子可以清晰地看到,java.sql.Date 对象只保留了日期部分。
如何正确处理带时间的日期?
既然 java.sql.Date 不能存储时间,那么当你的业务需要存储日期和时间时,应该使用什么类型呢?
使用 java.sql.Timestamp (JDBC 时代)
java.sql.Timestamp 是 JDBC 提供的专门用于与 SQL TIMESTAMP 类型(或 DATETIME 类型)映射的类,它可以精确到纳秒级别。
使用场景:
当你需要将一个包含时间信息的 java.util.Date 存入数据库的 TIMESTAMP 或 DATETIME 字段时,应该使用 java.sql.Timestamp。

示例代码:
import java.sql.Timestamp;
import java.util.Date as UtilDate;
public class SqlTimestampExample {
public static void main(String[] args) {
// 1. 创建一个包含完整时间信息的 java.util.Date
UtilDate utilDate = new UtilDate();
System.out.println("原始 java.util.Date: " + utilDate);
// 2. 将 java.util.Date 转换为 java.sql.Timestamp
// Timestamp 会完整保留时间信息
Timestamp timestamp = new Timestamp(utilDate.getTime());
System.out.println("转换后的 java.sql.Timestamp: " + timestamp);
// 输出类似: 2025-10-27 15:30:45.123
// 3. 从 java.sql.Timestamp 转换回 java.util.Date
UtilDate dateFromTimestamp = new Date(timestamp.getTime());
System.out.println("从 Timestamp 转换回 java.util.Date: " + dateFromTimestamp);
// 输出类似: Fri Oct 27 15:30:45 CST 2025
}
}
JDBC 操作示例:
// 假设 conn 是一个有效的数据库连接
String sql = "INSERT INTO events (event_name, event_time) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "系统启动");
pstmt.setTimestamp(2, new Timestamp(new java.util.Date().getTime())); // 使用 setTimestamp
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
使用 java.time 包 (现代 Java 8+ 推荐)
自 Java 8 引入 java.time 包后,它已经成为处理日期和时间的标准、推荐方式,它比旧的 java.util.Date 和 java.sql.* 类更安全、更易用。
LocalDate: 对应 SQLDATE,只表示日期(年、月、日)。LocalDateTime: 对应 SQLTIMESTAMP或DATETIME,表示日期和时间,但不带时区信息。ZonedDateTime: 带有时区信息的日期时间。
使用场景:
在业务逻辑层,强烈建议使用 java.time 中的类,在与数据库交互时,JDBC 4.2+ 规范可以直接使用这些 java.time 类型,JDBC 驱动会自动进行转换。
示例代码:
import java.sql.*;
import java.time.*;
public class JavaTimeExample {
public static void main(String[] args) {
// 1. 使用 java.time.LocalDateTime 表示一个带时间的日期
LocalDateTime now = LocalDateTime.now();
System.out.println("当前的 LocalDateTime: " + now);
// 输出类似: 2025-10-27T15:30:45.123
// 2. JDBC 4.2+ 中可以直接使用 LocalDateTime
// 假设 conn 是一个有效的数据库连接
String sql = "INSERT INTO events (event_name, event_time) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "定时任务执行");
// 直接使用 setObject 传入 LocalDateTime
pstmt.setObject(2, now);
pstmt.executeUpdate();
System.out.println("数据插入成功!");
} catch (SQLException e) {
e.printStackTrace();
}
// 3. 从数据库读取时,也可以直接获取为 LocalDateTime
String querySql = "SELECT event_time FROM events WHERE event_name = ?";
try (PreparedStatement pstmt = conn.prepareStatement(querySql)) {
pstmt.setString(1, "定时任务执行");
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
// 直接使用 getObject 获取为 LocalDateTime
LocalDateTime eventTime = rs.getObject("event_time", LocalDateTime.class);
System.out.println("从数据库读取到的时间: " + eventTime);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
总结与最佳实践
| 类型 | 对应 SQL 类型 | 包含信息 | 推荐使用场景 | 备注 |
|---|---|---|---|---|
java.sql.Date |
DATE |
仅日期 (年-月-日) | 不推荐,仅在与旧版 JDBC 或遗留代码交互时使用。 | 会丢失时间信息。 |
java.sql.Timestamp |
TIMESTAMP / DATETIME |
日期 + 时间 (纳秒级) | 旧版项目 (Java 8 之前),当必须使用 JDBC 类型时,用它来处理时间。 | 是 java.util.Date 的子类,但行为特殊。 |
java.time.LocalDate |
DATE |
仅日期 (年-月-日) | 推荐,现代 Java (8+) 业务逻辑中处理日期。 | 首选,类型安全,不可变。 |
java.time.LocalDateTime |
TIMESTAMP / DATETIME |
日期 + 时间 (纳秒级) | 强烈推荐,现代 Java (8+) 业务逻辑中处理日期和时间。 | 首选,类型安全,不可变,JDBC 4.2+ 支持直接使用。 |
最终建议:
- 对于新项目:完全放弃
java.sql.Date和java.sql.Timestamp,在业务代码中统一使用java.time包下的LocalDate和LocalDateTime。 - 对于数据库交互:如果你的 JDBC 驱动版本是 4.2 或更高,直接使用
pstmt.setObject()和rs.getObject()来读写LocalDate和LocalDateTime,这是最优雅的方式。 - 对于旧项目维护:如果必须使用
java.sql.Date,请务必记住它没有时间,并且在转换时主动处理时间信息的丢失或保留问题,如果需要时间,请使用java.sql.Timestamp。
