JDBC ResultSet:我需要一个 getDateTime,但只有 getDate 和 getTimeStamp
Posted
技术标签:
【中文标题】JDBC ResultSet:我需要一个 getDateTime,但只有 getDate 和 getTimeStamp【英文标题】:JDBC ResultSet: I need a getDateTime, but there is only getDate and getTimeStamp 【发布时间】:2014-02-05 10:00:48 【问题描述】:我想使用 JDBC 从 Oracle 数据库表中获取 DATETIME 列。这是我的代码:
int columnType = rsmd.getColumnType(i);
if(columnType == Types.DATE)
Date aDate = rs.getDate(i);
valueToInsert = aDate.toString();
else if(columnType == Types.TIMESTAMP)
Timestamp aTimeStamp = rs.getTimestamp(i);
valueToInsert = aTimeStamp.toString();
else
valueToInsert = rs.getString(i);
我必须首先确定列类型。我感兴趣的字段被识别为 Types.DATE,但它实际上是数据库中的 DATETIME,因为它具有以下格式:“07.05.2009 13:49:32”
getDate 截断时间:“07.05.2009” getString 将“.0”附加到它:“07.05.2009 13:49:32.0”
当然,我可以只删除最后的 .0 并一直使用 getString,但这是一个肮脏的解决方法。
有什么想法吗?我正在寻找一个 getDateTime 方法。
【问题讨论】:
很确定getTimestamp()
应该可以满足您的需求。
那么类似于 if(columnType == Types.DATE) 然后使用 rs.getTimestamp(i) ?我会尝试 - 但这不会破坏不同类型的目的吗?
我刚试过,没用——它返回的结果和getString一样:valueToInsert = 2009-05-07 13:49:32.0
你为什么要转换成字符串?从 ResultSet 中检索到该值后,我真的不明白您要如何处理它。
我把它写在一个文本文件中。此代码是报告生成软件的一部分。其他人在其他地方使用的报告,包括导入其他数据库(这就是漏洞被发现的方式)
【参考方案1】:
这个答案已经过时了。继续Basil Bourque's answer。
java.util.Date date;
Timestamp timestamp = resultSet.getTimestamp(i);
if (timestamp != null)
date = new java.util.Date(timestamp.getTime()));
然后按照你喜欢的方式格式化它。
【讨论】:
一个简单的演员阵容不也可以吗?public class Timestamp extends java.util.Date
使用此答案时请注意时区。某些驱动程序(绝对是 JTDS)的 getTimestamp() 可以默认为当前计算机的时区,如果您的数据库将时间戳存储为年-月-日时:分:秒,这将使您得到错误的值。请注意,有一个超载的日历可能不会出现问题。
@Trejkaz 你建议如何克服你提到的问题?
@theyuv 我们打算通过将所有时间戳列切换到BIGINT
并在客户端代码中使用Instant
来避免它,这样时区就不必出现在图片中。 (而且,正如我已经提到的,有 Calendar
重载,有些人声称它适用于某些数据库,但我更愿意尽可能放弃旧的日期/时间 API。)
但是你仍然需要知道 bigint 值的时区,不是吗?【参考方案2】:
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 类型 专有类型 JDBC 类型SQL 标准类型
SQL 标准defines five types:
DATE
TIME WITHOUT TIME ZONE
TIME WITH TIME ZONE
TIMESTAMP WITHOUT TIME ZONE
TIMESTAMP WITH TIME ZONE
仅限日期
DATE
只有日期,没有时间,没有时区。
仅限时间
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:
getDate
为 java.sql.Date
getTime
用于 java.sql.Time
getTimestamp
用于 java.sql.Timestamp
请注意,第一个缺少任何时区或与 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 类?
Java SE 8、Java SE 9、Java SE 10 及更高版本 内置。 标准 Java API 的一部分,带有捆绑实现。 Java 9 添加了一些小功能和修复。 Java SE 6 和 Java SE 7 ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。 Android java.time 类的 android 捆绑包实现的更高版本。 对于早期的 Android (ThreeTenABP 项目采用 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…。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
【讨论】:
我将 JTDS 与 MSSQL 一起使用,当我执行 rs.getString("000123450") 时,它会删除所有前导零并在输出中给我 123450。我怎样才能得到像 000123450 这样的整个值?有什么帮助吗?谢谢,Harshita @user4567570 请重新阅读我的回答。通常,您不应该将字符串用于日期时间工作。在数据库中使用日期时间类型,在 JDBC 代码中使用日期时间类型 (java.sql.*),在 Java 代码中使用日期时间类型 (java.time.*)。这是一般建议,因为我不熟悉 JTDS 和 MSSQL。 Java 8 的更新,因为它现在已经成为现实,这将使这个答案真正闪耀......轻推轻推眨眼。 @Namphibian 问,你会收到的。 我知道这是几年后的事了,但对于其他找到这个答案的人来说......这是一个很好的答案,但有一个问题。 java.time.Instant 的用途很少。作为一般规则,不要使用它。请改用 OffsetDateTime。 Instant 与 UTC 时间标准基本相同(不是 UTC 零偏移时区)。 OffsetDateTime 是指日历和时钟指示的时间。这与 UTC 不同。几乎所有时间的使用都与日历和时钟时间有关,而不是原子时间标准。 OffsetDateTime 也更有能力。【参考方案3】:我选择的解决方案是使用 mysql 查询格式化日期:
String l_mysqlQuery = "SELECT DATE_FORMAT(time, '%Y-%m-%d %H:%i:%s') FROM uld_departure;"
l_importedTable = fStatement.executeQuery( l_mysqlQuery );
System.out.println(l_importedTable.getString( timeIndex));
我遇到了完全相同的问题。
即使我的 mysql 表包含格式如下的日期:2017-01-01 21:02:50
String l_mysqlQuery = "SELECT time FROM uld_departure;"
l_importedTable = fStatement.executeQuery( l_mysqlQuery );
System.out.println(l_importedTable.getString( timeIndex));
返回的日期格式如下:
2017-01-01 21:02:50.0
【讨论】:
【参考方案4】:这行得通:
Date date = null;
String dateStr = rs.getString("doc_date");
if (dateStr != null)
date = dateFormat.parse(dateStr);
使用 SimpleDateFormat。
【讨论】:
以上是关于JDBC ResultSet:我需要一个 getDateTime,但只有 getDate 和 getTimeStamp的主要内容,如果未能解决你的问题,请参考以下文章
JDBC 驱动程序在空 ResultSet 上抛出“ResultSet Closed”异常
何时关闭 JDBC 中的 Connection、Statement、PreparedStatement 和 ResultSet