错过了在Java 8中修复JDBC日期处理的机会?

任何Java 8 + JDBC专家都能告诉我以下推理是否有问题吗? 而且,如果在众神的秘密中,为什么没有这样做呢?

java.sql.Date当前是JDBC用于映射到DATE SQL类型的类型,它表示没有时间且没有时区的日期。 但是这个类设计得非常好,因为它实际上是java.util.Date的子类,它可以存储精确的瞬间,最长可达毫秒。

为了在数据库中表示日期2015-09-13,我们因此被迫选择一个时区,在该时区中将字符串“2015-09-13T00:00:00.000”解析为java.util.Date以获得毫秒value,然后从这个毫秒值构造一个java.sql.Date ,最后在预准备语句上调用setDate() ,传递一个包含所选时区的Calendar,以便JDBC驱动程序能够正确地重新计算日期2015-09 -13来自这个毫秒值。 通过在任何地方使用默认时区,而不是传递日历,可以使此过程更简单一些。

Java 8引入了一个LocalDate类,它更适合DATE数据库类型,因为它不是精确的时刻,因此不依赖于时区。 Java 8还引入了默认方法,这些方法允许对PreparedStatement和ResultSet接口进行向后兼容的更改。

那么,我们还没有错过一个清理JDBC中的混乱而仍然保持向后兼容性的巨大机会吗? Java 8可以简单地将这些默认方法添加到PreparedStatement和ResultSet:

 default public void setLocalDate(int parameterIndex, LocalDate localDate) { if (localDate == null) { setDate(parameterIndex, null); } else { ZoneId utc = ZoneId.of("UTC"); java.util.Date utilDate = java.util.Date.from(localDate.atStartOfDay(utc).toInstant()); Date sqlDate = new Date(utilDate.getTime()); setDate(parameterIndex, sqlDate, Calendar.getInstance(TimeZone.getTimeZone(utc))); } } default LocalDate getLocalDate(int parameterIndex) { ZoneId utc = ZoneId.of("UTC"); Date sqlDate = getDate(parameterIndex, Calendar.getInstance(TimeZone.getTimeZone(utc))); if (sqlDate == null) { return null; } java.util.Date utilDate = new java.util.Date(sqlDate.getTime()); return utilDate.toInstant().atZone(utc).toLocalDate(); } 

当然,相同的推理适用于TIM对TIMESTAMP类型的Instant支持,以及LocalTime对TIME类型的支持。

为了在数据库中表示日期2015-09-13,我们因此被迫选择一个时区,在该时区中将字符串“2015-09-13T00:00:00.000”解析为java.util.Date以获得毫秒value,然后从这个毫秒值构造一个java.sql.Date,最后在预准备语句上调用setDate(),传递一个包含所选时区的Calendar,以便JDBC驱动程序能够正确地重新计算日期2015-09 -13来自这个毫秒值

为什么? 打电话吧

 Date.valueOf("2015-09-13"); // From String Date.valueOf(localDate); // From java.time.LocalDate 

所有JDBC驱动程序中的行为都是正确的:没有时区的本地日期。 逆操作是:

 date.toString(); // To String date.toLocalDate(); // To java.time.LocalDate 

你永远不应该依赖于java.sql.Datejava.util.Date的依赖,以及它因此通过Date(long)Date.getTime()inheritancejava.time.Instant的语义这一事实。

那么,我们还没有错过一个清理JDBC中的混乱而仍然保持向后兼容性的巨大机会吗? […]

这取决于。 JDBC 4.2规范指定您能够通过setObject(int, localDate)绑定LocalDate类型,并通过getObject(int, LocalDate.class)获取LocalDate类型getObject(int, LocalDate.class)如果驱动程序已为此做好准备)。 当然,正如您所建议的那样,并不像更正式的default方法那样优雅。

简短回答:不,日期时间处理在Java 8 / JDBC 4.2中得到修复,因为它支持Java 8日期和时间类型 。 无法使用默认方法模拟此操作。

Java 8可以简单地将这些默认方法添加到PreparedStatement和ResultSet:

由于以下几个原因,这是不可能的:

  • java.time.OffsetDateTimejava.sql没有等价物
  • 转换为java.sql.Timestamp时, java.time.LocalDateTime在DST转换时中断,因为后者依赖于JVM时区,请参阅下面的代码
  • java.sql.Time具有毫秒级的分辨率,但java.time.LocalTime具有纳秒分辨率

因此,您需要适当的驱动程序支持,默认方法必须通过引入数据丢失的java.sql类型进行转换。

如果您在具有夏令时的JVM时区中运行此代码,它将会中断。 它搜索时钟“向前”的下一个转换,并选择正好在转换过程中的LocalDateTime 。 这是完全有效的,因为Java LocalDateTime或SQL TIMESTAMP没有时区,因此没有时区规则,因此没有夏令时。 另一方面, java.sql.Timestamp绑定到JVM时区,因此受夏令时限制。

 ZoneId systemTimezone = ZoneId.systemDefault(); Instant now = Instant.now(); ZoneRules rules = systemTimezone.getRules(); ZoneOffsetTransition transition = rules.nextTransition(now); assertNotNull(transition); if (!transition.getDateTimeBefore().isBefore(transition.getDateTimeAfter())) { transition = rules.nextTransition(transition.getInstant().plusSeconds(1L)); assertNotNull(transition); } Duration gap = Duration.between(transition.getDateTimeBefore(), transition.getDateTimeAfter()); LocalDateTime betweenTransitions = transition.getDateTimeBefore().plus(gap.dividedBy(2L)); Timestamp timestamp = java.sql.Timestamp.valueOf(betweenTransitions); assertEquals(betweenTransitions, timestamp.toLocalDateTime());