任何Java 8 + JDBC专家都能告诉我以下推理中是否存在错误?而且,如果在众神的秘密中,为什么还没有完成呢?
java.sql.Date
当前是JDBC用于映射到DATE SQL类型的类型,它表示没有时间且没有时区的日期。但是这个类设计得很好,因为它实际上是java.util.Date
的子类,它可以存储精确的瞬间,直到毫秒。
为了在数据库中表示日期2015-09-13,我们因此被迫选择时区,解析字符串" 2015-09-13T00:00:00.000"在该时区作为java.util.Date得到一个毫秒值,然后从这个毫秒值构造一个java.sql.Date
,最后在准备好的语句上调用setDate()
,传递一个包含所选时区的日历命令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类型的支持。
答案 0 :(得分:25)
为了在数据库中表示日期2015-09-13,我们因此被迫选择时区,将该时区中的字符串“2015-09-13T00:00:00.000”解析为java.util.Date得到一个毫秒值,然后从这个毫秒值构造一个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.Date
对java.util.Date
的依赖,以及它因此通过java.time.Instant
或{{3}继承Date(long)
的语义这一事实}
那么,难道我们还没有错过在清除后向兼容性的同时清理JDBC中的混乱的巨大机会吗? [...]
这取决于。 Date.getTime()
指定您可以通过JDBC 4.2 spec绑定LocalDate
类型,并通过setObject(int, localDate)
获取LocalDate
类型,如果驱动程序已准备好那。当然,正如您所建议的那样,不如更正式的default
方法更优雅。
答案 1 :(得分:6)
简答:不,日期时间处理在Java 8 / JDBC 4.2中得到修复,因为它支持Java 8 Date and Time types。无法使用默认方法进行模拟。
Java 8可以简单地将这些默认方法添加到PreparedStatement和ResultSet:
由于以下几个原因,这是不可能的:
java.time.OffsetDateTime
在java.sql
java.time.LocalDateTime
时,java.sql.Timestamp
在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());