核心概念:它们是什么?
数据库中的 DATE
在几乎所有关系型数据库(如 MySQL, PostgreSQL, Oracle, SQL Server)中,DATE 是一个标准的数据类型。

- :它只存储日期部分,即 年、月、日,不包含时间信息(时、分、秒、毫秒)。
- 示例:
2025-10-27 - 相关类型:
DATETIME/TIMESTAMP:存储日期和时间。TIME:只存储时间。
Java 中的 Date
Java的日期时间API比较复杂,主要有三个核心类,很容易混淆:
-
java.util.Date(旧API)- :它实际上是一个时间戳,精确到毫秒,它同时包含了日期和时间信息。
- 设计缺陷:它的名称具有误导性,它不仅仅是一个 "Date"。
- 状态:已过时,不推荐在新代码中使用,主要用于兼容旧的代码库。
-
java.sql.Date(JDBC API)- 设计目的:专门用于与数据库交互。
- :它继承自
java.util.Date,但通过重写相关方法,只保留了日期部分,时间部分被忽略。 - 核心方法:
valueOf(String sqlDate)和toString()用于与数据库的DATE格式(yyyy-MM-dd)进行转换。 - 状态:仍然是JDBC规范的一部分,但处理起来很繁琐,不推荐首选。
-
java.time.LocalDate(新API - Java 8+)
(图片来源网络,侵删)- 设计目的:作为
java.util.Date的现代化替代品,解决了其所有设计缺陷。 - :一个不可变的对象,只表示日期(年、月、日),不包含时间或时区信息。
- 优点:API清晰、线程安全、功能强大。
- 状态:强烈推荐在所有新项目中使用。
- 设计目的:作为
数据类型映射与转换
这是问题的核心,当Java与数据库交互时,需要在这两种类型之间进行转换。
Java -> 数据库 (存入)
目标:将Java中的日期对象存入数据库的 DATE 列。
| Java 类型 | 推荐做法 | JDBC 方法 | 说明 |
|---|---|---|---|
java.time.LocalDate (推荐) |
LocalDate localDate = ...;preparedStatement.setDate(1, java.sql.Date.valueOf(localDate)); |
preparedStatement.setDate(int, java.sql.Date) |
最佳实践,先转换为 java.sql.Date,然后使用 setDate 方法。valueOf 是 java.sql.Date 提供的便捷方法。 |
java.util.Date (旧) |
java.util.Date utilDate = ...;java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());preparedStatement.setDate(1, sqlDate); |
preparedStatement.setDate(int, java.sql.Date) |
需要手动从 util.Date 中提取日期部分,创建 sql.Date 对象,容易出错,不推荐。 |
java.sql.Date (JDBC) |
java.sql.Date sqlDate = ...;preparedStatement.setDate(1, sqlDate); |
preparedStatement.setDate(int, java.sql.Date) |
直接使用,但 java.sql.Date 本身API不友好,通常也是从 LocalDate 转换而来。 |
示例代码 (使用 LocalDate):
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.LocalDate;
public class InsertDateExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database";
String user = "your_username";
String password = "your_password";
// 1. 创建一个 Java 8 的 LocalDate 对象
LocalDate today = LocalDate.now();
String sql = "INSERT INTO events (event_name, event_date) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 2. 设置参数
pstmt.setString(1, "Java 8 Launch Party");
// 3. 转换为 java.sql.Date 并设置
pstmt.setDate(2, java.sql.Date.valueOf(today));
// 4. 执行
int affectedRows = pstmt.executeUpdate();
System.out.println(affectedRows + " row(s) inserted.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
数据库 -> Java (读取)
目标:从数据库的 DATE 列读取数据到Java对象。

| Java 类型 | 推荐做法 | JDBC 方法 | 说明 |
|---|---|---|---|
java.time.LocalDate (推荐) |
java.sql.Date sqlDate = resultSet.getDate("event_date");LocalDate localDate = sqlDate.toLocalDate(); |
resultSet.getDate(String) |
最佳实践,先用 getDate 从结果集中获取 java.sql.Date,然后调用其 toLocalDate() 方法,得到现代化的 LocalDate。 |
java.util.Date (旧) |
java.util.Date utilDate = resultSet.getDate("event_date"); |
resultSet.getDate(String) |
getDate() 方法返回的就是 java.sql.Date,它可以直接赋值给 java.util.Date(多态),但这样会丢失日期信息以外的上下文,且对象类型不纯。 |
java.sql.Date (JDBC) |
java.sql.Date sqlDate = resultSet.getDate("event_date"); |
resultSet.getDate(String) |
直接使用,但同样不推荐作为首选业务逻辑对象。 |
示例代码 (使用 LocalDate):
import java.sql.*;
import java.time.LocalDate;
public class SelectDateExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database";
String user = "your_username";
String password = "your_password";
String sql = "SELECT event_name, event_date FROM events WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 1); // 假设要查询 id=1 的记录
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String eventName = rs.getString("event_name");
// 1. 从结果集获取 java.sql.Date
java.sql.Date sqlDateFromDb = rs.getDate("event_date");
// 2. 转换为 java.time.LocalDate
LocalDate eventDate = sqlDateFromDb.toLocalDate();
System.out.println("Event: " + eventName);
System.out.println("Date: " + eventDate); // 输出格式: 2025-10-27
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
最佳实践总结
-
首选
java.time.LocalDate:- 在你的业务逻辑层、服务层和表现层,统一使用
java.time.LocalDate来处理纯日期数据,它的API更直观、更安全。 LocalDate.now()获取当前日期。LocalDate.of(year, month, day)创建指定日期。date1.isAfter(date2)进行日期比较。
- 在你的业务逻辑层、服务层和表现层,统一使用
-
在数据库交互层进行转换:
- 写入数据库:在
PreparedStatement设置参数时,将LocalDate通过java.sql.Date.valueOf()转换为java.sql.Date,然后调用setDate()。 - 读取数据库:在
ResultSet获取数据时,使用getDate()得到java.sql.Date,然后调用toLocalDate()转换回LocalDate。
- 写入数据库:在
-
避免
java.util.Date:- 除非你正在维护一个非常老的、无法重构的代码库,否则尽量避免在新的业务代码中使用
java.util.Date,它的设计缺陷(如从0开始月份、年份从1900开始)会带来很多麻烦。
- 除非你正在维护一个非常老的、无法重构的代码库,否则尽量避免在新的业务代码中使用
-
数据库列类型选择:
- 如果你只需要存储年、月、日,请务必在数据库中使用
DATE类型。 - 如果你需要存储日期和时间,请使用
DATETIME或TIMESTAMP,Java端对应的应该是java.time.LocalDateTime。
- 如果你只需要存储年、月、日,请务必在数据库中使用
常见陷阱与注意事项
-
时区问题:
java.sql.Date和LocalDate都不包含时区信息,它们代表的是“虚拟时区”(如系统默认时区)中的日期。- 当你的应用程序和数据库服务器位于不同的时区时,这可能会导致问题,在UTC+8的服务器上存储的日期,在UTC-5的服务器上读取时,日期可能会“提前一天”。
- 解决方案:确保你的应用服务器和数据库服务器的时区设置一致,或者在你的应用逻辑中统一使用UTC时间进行计算,只在最终展示时转换为本地时区。
-
java.util.Datevsjava.sql.Date的混淆:new java.util.Date().getTime()返回的是一个包含时间戳的毫秒数。new java.sqlDate(utilDate.getTime())会丢弃这个时间戳中的时间部分。- 反过来,
java.sqlDate.getTime()返回的毫秒数,其时间部分都是00:00:00,直接用它创建java.util.Date是不准确的。
-
ORM框架(如JPA/Hibernate)的处理:
-
使用JPA时,你可以在实体类中直接使用
java.time.LocalDate,并通过@Temporal(TemporalType.DATE)注解来告诉JPA如何映射。 -
@Entity public class Event { @Id private Long id; private String eventName; @Temporal(TemporalType.DATE) private LocalDate eventDate; // JPA会自动处理与数据库DATE类型的转换 } -
使用ORM框架可以大大简化这些手动的转换工作。
-
