随着时间的推移,当前的事态
自从 JDBC 被发明并对其接口进行标准化以来,已经过去了 20 年,在此期间发生了很多变化。
首先,世界已经全球化,现在一台服务器可以为来自世界各地的用户提供服务。网速上来了。因此,另一种数据类型被添加到 SQL 中以处理时间。现在类型看起来像这样:
- DATE - 存储日期:年、月、日。
- TIME - 存储时间:小时、分钟、秒。
- TIMESTAMP - 存储特定时间点:日期、时间和毫秒。
- TIMESTAMP WITH TIME ZONE - 时间戳和时区(时区名称或偏移量)。
其次,Java 引入了用于全局时间管理的 DateTime API。它有以下类:
- 日期和时间:
- 本地日期
- 当地时间
- 确切时刻:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
- 带时区的时间:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
第三个有趣的点是许多 SQL 客户端希望从已经在其本地区域中的服务器接收时间。当然,你可以即时转换时间,但不方便,而且会出错。
例如,我想从数据库中获取今天的所有任务。SQL Server为此提供了一个CURDATE()函数。只有这里服务器在美国,而我在日本。我希望他返回“我的今天”而不是“他的今天”的所有记录。
一般来说,SQL服务器还必须能够与不同时区的客户端进行智能协作。
现代问题需要现代解决方案
原则上,可以方便地映射来自 Java DateTime API 的新类型和来自 SQL 的类型。要在 Java 中表示DATE类型,您需要使用JDK 8 DateTime API 中的java.time.LocalDate类。
数据库中的 TIME 类型可以用 Java 中的两种类型表示:java.time.LocalTime和java.time.OffsetTime。也没什么复杂的。
一个特定的时间点,在数据库中用TIMESTAMP类型来表示,在Java中可以用4种类型来表示:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
最后,TIMESTAMP WITH TIME ZONE可以用两种类型表示:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
由于您已经熟悉 DateTime API,因此记住这件事对您来说并不困难 :)
我会以表格的形式写出来,这样会更容易:
类型 | Java类型 |
---|---|
日期 | java.time.LocalDate |
时间 | java.time.LocalTime java.time.OffsetTime |
时间戳 | java.time.Instant java.time.LocalDateTime java.time.OffsetDateTime java.time.ZonedDateTime |
带时区的时间戳 | java.time.OffsetDateTime _ |
获取日期
我有一个好消息要告诉你。很长一段时间以来第一次。我们可以绕过getDate()方法的限制,它返回一个 java.sql 日期类型。
重点是对象结果集还有另一个有趣的方法 - getObject()。此方法有两个参数:一个列和一个类型,并返回转换为给定类型的列的值。该方法的一般形式如下:
ClassName Name = getObject(column, ClassName);
如果你想将DATE类型转换为java.time.LocalDate类型,那么你需要这样写:
LocalDate localDate = results.getObject(4, LocalDate.class);
一般而言,任何 TIMESTAMP 都可以转换为多种类型:
java.time.Instant instant = results.getObject(9, java.time.Instant.class);
java.time.LocalDateTime local = results.getObject(9, java.time. LocalDateTime.class);
java.time.OffsetDateTime offset = results.getObject(9, java.time. OffsetDateTime.class);
java.time.ZonedDateTime zoned = results.getObject(9, java.time. ZonedDateTime.class);
重要的! 如果您有过时的 MySQL JDBC 驱动程序,此代码将不起作用。注意在你的 pom.xml 中编写的“mysql-connector-java”的版本,或者添加到项目设置中的库中。
顺便说一句,以同样的方式,您可以解决无法为原始类型存储 null 的问题。如果表列的类型为 INT,则有几种方法可以从中获取空值。请参见下面的示例:
Integer id1 = results.getObject(8, Integer.class); // this will work
Integer id2 = results.getObject(8, int.class); //this will also work
int id3 = results.getObject(8, Integer.class); //method will return null, JVM will throw NPE
int id4 = results.getObject(8, int.class); //method will return null, JVM will throw NPE
MySQL 中的时区设置
MySQL 也发生了很多有趣的事情。如您所知,在创建 MySQL 连接时,您可以向其添加各种 参数:mysql://localhost:3306/db_scheme?Name=meaning&Name=meaning
因此,添加了三个参数来处理 MySQL 中的时区。您可以在与服务器建立连接时传递这些参数。
下面我将与他们一起给出一个表格:
范围 | 价值观 | 默认值 |
---|---|---|
连接时区 | 本地 | 服务器 | 用户区 | 服务器 |
forceConnectionTimeZoneToSession | 真 | 错误的 | 真的 |
保存瞬间 | 真 | 错误的 | 错误的 |
使用connectionTimeZone参数,我们选择将执行所有请求的时区(time zone)。从客户端的角度来看,服务器正在指定的时区运行。
forceConnectionTimeZoneToSession参数导致会话 time_zone 变量被忽略并替换为 connectionTimeZone。
最后,preserveInstants参数控制 JVM 的 timeZone 和 connectionTimeZone 之间的精确时间转换。
最常见的配置是:
-
connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=false - 对应于 useLegacyDatetimeCode=true 的旧 MySQL JDBC 驱动程序版本 5.1。
-
connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=true是一种新模式,它提供了处理日期和时间值的最自然方式。
-
connectionTimeZone=SERVER & preserveInstants=true - 对应于 useLegacyDatetimeCode=false 的旧 MySQL JDBC 驱动程序版本 5.1。
-
connectionTimeZone=user_defined & preserveInstants=true - 帮助克服连接器无法识别服务器时区的情况,因为它被设置为通用缩写,例如 CET/CEST。
是的,日期是一个有趣的话题,并且有很多问题。俗话说:当然很可怕,但我也不生气!:)
GO TO FULL VERSION