随着时间的推移,当前的事态

自从 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.LocalTimejava.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。

是的,日期是一个有趣的话题,并且有很多问题。俗话说:当然很可怕,但我也不生气!:)