answer by Leos Literak 是正确的,但现在已过时,使用的是麻烦的旧日期时间类之一,java.sql.Timestamp。
tl;博士
这确实是数据库中的 DATETIME
不,不是。 Oracle数据库中没有DATETIME这样的数据类型。
我正在寻找一个 getDateTime 方法。
在 JDBC 4.2 及更高版本中使用 java.time 类,而不是在您的问题中看到麻烦的遗留类。特别是,暂时不要使用java.sql.TIMESTAMP,而是使用Instant 类,例如SQL 标准类型TIMESTAMP WITH TIME ZONE。
人为的代码sn-p:
if(
JDBCType.valueOf(
myResultSetMetaData.getColumnType( … )
)
.equals( JDBCType.TIMESTAMP_WITH_TIMEZONE )
) {
Instant instant = myResultSet.getObject( … , Instant.class ) ;
}
奇怪的是,JDBC 4.2 规范不需要支持两个最常用的 java.time 类,Instant 和 ZonedDateTime。因此,如果您的 JDBC 不支持上述代码,请改用 OffsetDateTime。
OffsetDateTime offsetDateTime = myResultSet.getObject( … , OffsetDateTime.class ) ;
详情
我想使用 JDBC 从 Oracle 数据库表中获取 DATETIME 列。
根据this doc,Oracle数据库中没有列数据类型DATETIME。该术语似乎是 Oracle 将所有日期时间类型称为一个组的词。
我没有看到您的代码检测类型和分支的数据类型的要点。一般来说,我认为您应该在特定表和特定业务问题的上下文中明确地编写代码。也许这在某种通用框架中会很有用。如果您坚持,请继续阅读以了解各种类型,并了解 Java 8 及更高版本中内置的非常有用的新 java.time 类,这些类取代了您的问题中使用的类。
智能对象,而不是哑字符串
valueToInsert = aDate.toString();
您似乎试图以文本形式与数据库交换日期时间值,例如 String 对象。别。
要与数据库交换日期时间值,请使用日期时间对象。现在在 Java 8 及更高版本中,这意味着 java.time 对象,如下所述。
各种类型系统
您可能会混淆三组与日期时间相关的数据类型:
SQL 标准类型
SQL 标准defines five types:
DATE
TIME WITHOUT TIME ZONE
TIME WITH TIME ZONE
TIMESTAMP WITHOUT TIME ZONE
TIMESTAMP WITH TIME ZONE
仅限日期
仅限时间
-
TIME 或 TIME WITHOUT TIME ZONE
只有时间,没有日期。静默忽略作为输入的一部分指定的任何时区。
-
TIME WITH TIME ZONE(或TIMETZ)
只有时间,没有日期。如果输入中包含足够的数据,则应用时区和夏令时规则。考虑到其他数据类型(如 discussed in Postgres doc),其有用性值得怀疑。
日期和时间
-
TIMESTAMP 或 TIMESTAMP WITHOUT TIME ZONE
日期和时间,但忽略时区。传递给数据库的任何时区信息都会被忽略,不会对 UTC 进行调整。因此,这不代表时间轴上的特定时刻,而是代表大约 26-27 小时内的一系列可能时刻。如果时区或偏移量 (a) 未知或 (b) 不相关,例如“我们在世界各地的所有工厂在中午关闭午餐”,请使用此选项。 If you have any doubts, not likely the right type。
-
TIMESTAMP WITH TIME ZONE(或TIMESTAMPTZ)
与时区相关的日期和时间。请注意,根据实现的不同,这个名称有点用词不当。某些系统可能会存储给定的时区信息。在其他系统(例如Postgres)中,时区信息存储,而是使用传递到数据库的时区信息将日期时间调整为 UTC。
专有
许多数据库提供自己的日期时间相关类型。专有类型变化很大。有些是应该避免的旧的遗留类型。供应商认为有些可以提供某些好处;您决定是否仅使用标准类型。注意:某些专有类型的名称与标准类型冲突;我在看着你Oracle DATE。
JDBC
Java 平台处理日期时间的内部细节与 SQL 标准或特定数据库不同。 JDBC driver 的工作是在这些差异之间进行调解,充当桥梁,根据需要翻译类型及其实际实现的数据值。 java.sql.* package 就是那座桥。
JDBC 遗留类
在 Java 8 之前,JDBC 规范为日期时间工作定义了 3 种类型。前两个是版本 8 之前的 hack,Java 缺少任何类来表示仅日期或仅时间值。
-
java.sql.Date
模拟只有日期,假装没有时间,没有时区。可能会令人困惑,因为此类是 java.util.Date 的包装器,它跟踪日期 和 时间。在内部,时间部分设置为零(UTC 午夜)。
-
java.sql.Time
只有时间,假装没有日期,没有时区。也可能令人困惑,因为这个类也是 java.util.Date 的一个薄包装器,它跟踪日期 和 时间。在内部,日期设置为零(1970 年 1 月 1 日)。
-
java.sql.TimeStamp
日期和时间,但没有时区。这也是 java.util.Date 的薄包装。
这样就回答了您关于ResultSet interface 中没有“getDateTime”方法的问题。该接口为 JDBC 中定义的三种桥接数据类型提供getter methods:
请注意,第一个缺少任何时区或与 UTC 偏移的概念。最后一个,java.sql.Timestamp 始终使用 UTC,尽管它的 toString 方法告诉你什么。
JDBC 现代类
您应该避免上面列出的那些设计不佳的 JDBC 类。它们被 java.time 类型所取代。
- 请使用
LocalDate,而不是java.sql.Date。适合 SQL 标准的 DATE 类型。
- 使用
LocalTime 代替java.sql.Time。适合 SQL 标准的 TIME WITHOUT TIME ZONE 类型。
- 请使用
Instant,而不是java.sql.Timestamp。适合 SQL 标准的 TIMESTAMP WITH TIME ZONE 类型。
从 JDBC 4.2 及更高版本开始,您可以直接与数据库交换 java.time 对象。使用setObject/getObject 方法。
插入/更新。
myPreparedStatement.setObject( … , instant ) ;
检索。
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Instant 类表示UTC 中时间轴上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。
调整时区
如果您想通过特定地区(时区)的人们使用的挂钟时间而不是 UTC 来查看 Instant 的时刻,请通过应用 ZoneId 进行调整以获得ZonedDateTime 对象。
ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;
生成的ZonedDateTime 对象是同一时刻,时间轴上的同一同时点。新的一天在东方更早黎明,因此日期和时间会有所不同。例如,新西兰的午夜过后几分钟仍然是 UTC 中的“昨天”。
您可以将另一个时区应用于Instant 或ZonedDateTime,以通过其他地区的人们使用的另一个挂钟时间查看同一时刻。
ZoneId zMontréal = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdtMontréal = zdtAuckland.withZoneSameInstant( zMontréal ) ; // Or, for the same effect: instant.atZone( zMontréal )
所以现在我们有了三个对象(instant、zdtAuckland、zMontréal),它们都代表时间轴上的同一时刻、同一点。
检测类型
回到问题中关于检测数据库数据类型的代码:(a)不是我的专业领域,(b)我会避免上面提到的这种情况,以及(c)如果你坚持请注意,从 Java 8 及更高版本开始,java.sql.Types 类已过时。该类现在被一个正确的Java Enum 替换为JDBCType,它实现了新的接口SQLType。有关相关问题,请参阅 this Answer。
此更改列于JDBC Maintenance Release 4.2,第 3 和 4 节。引用:
增加 java.sql.JDBCType 枚举
用于标识通用 SQL 类型的枚举,称为 JDBC 类型。目的是使用 JDBCType 代替 Types.java 中定义的常量。
枚举与旧类具有相同的值,但现在提供type-safety。
关于语法的说明:在现代 Java 中,您可以在 Enum 对象上使用 switch。因此,无需使用您的问题中看到的级联 if-then 语句。一个问题是枚举对象的名称在切换时必须使用不合格的技术原因,因此您必须在TIMESTAMP_WITH_TIMEZONE 上使用switch,而不是合格的JDBCType.TIMESTAMP_WITH_TIMEZONE。使用static import 语句。
所以,这就是说我猜(我还没有尝试过)您可以执行以下代码示例。
final int columnType = myResultSetMetaData.getColumnType( … ) ;
final JDBCType jdbcType = JDBCType.valueOf( columnType ) ;
switch( jdbcType ) {
case DATE : // FYI: Qualified type name `JDBCType.DATE` not allowed in a switch, because of an obscure technical issue. Use a `static import` statement.
…
break ;
case TIMESTAMP_WITH_TIMEZONE :
…
break ;
default :
…
break ;
}
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。
更新:Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。本节原封不动地保留为历史。
乔达时间
在 Java 8(java.time.* 包)之前,与 java 捆绑的日期时间类(java.util.Date & Calendar、java.text.SimpleDateFormat)出了名的麻烦、混乱和缺陷。
更好的做法是获取 JDBC 驱动程序提供的内容并从中创建 Joda-Time 对象,或者在 Java 8 中,java.time.* package。最终,您应该会看到自动使用新 java.time.* 类的新 JDBC 驱动程序。在此之前,一些方法已添加到 java.sql.Timestamp 等类中以插入 java.time,例如 toInstant 和 fromInstant。
字符串
至于问题的后半部分,渲染一个String……应该使用formatter对象来生成字符串值。
老式的方法是使用 java.text.SimpleDateFormat。不推荐。
Joda-Time 提供各种内置格式化程序,您也可以定义自己的。但是对于您提到的编写日志或报告,最好的选择可能是 ISO 8601 格式。该格式恰好是 Joda-Time 和 java.time 使用的默认格式。
示例代码
//java.sql.Timestamp timestamp = resultSet.getTimestamp(i);
// Or, fake it
// long m = DateTime.now().getMillis();
// java.sql.Timestamp timestamp = new java.sql.Timestamp( m );
//DateTime dateTimeUtc = new DateTime( timestamp.getTime(), DateTimeZone.UTC );
DateTime dateTimeUtc = new DateTime( DateTimeZone.UTC ); // Defaults to now, this moment.
// Convert as needed for presentation to user in local time zone.
DateTimeZone timeZone = DateTimeZone.forID("Europe/Paris");
DateTime dateTimeZoned = dateTimeUtc.toDateTime( timeZone );
转储到控制台...
System.out.println( "dateTimeUtc: " + dateTimeUtc );
System.out.println( "dateTimeZoned: " + dateTimeZoned );
运行时……
dateTimeUtc: 2014-01-16T22:48:46.840Z
dateTimeZoned: 2014-01-16T23:48:46.840+01:00