JDBC ResultSet:我需要一个getDateTime,但只有getDate和getTimeStamptl;博士详智能对象,而不是哑字符串各种类型系统关于 java.time

2022-08-31 20:44:24

我想从带有JDBC的Oracle DB表中获取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方法。


答案 1

这个答案已经过时了。继续阅读Basil Bourque的答案

java.util.Date date;
Timestamp timestamp = resultSet.getTimestamp(i);
if (timestamp != null)
    date = new java.util.Date(timestamp.getTime()));

然后按照您喜欢的方式进行格式化。


答案 2

Leos Literak的答案是正确的,但现在已经过时了,使用了一个麻烦的旧日期时间类, 。java.sql.Timestamp

tl;博士

它实际上是数据库中的一个日期时间

不,它不是。没有 Oracle 数据库中的数据类型。DATETIME

我正在寻找一个getDateTime方法。

在 JDBC 4.2 及更高版本中使用 java.time 类,而不是在 Question 中看到的麻烦的旧版类。特别是,不要使用类,例如 SQL 标准类型 。java.sql.TIMESTAMPInstantTIMESTAMP WITH TIME ZONE

人为的代码片段:

if( 
    JDBCType.valueOf( 
        myResultSetMetaData.getColumnType( … )
    )
    .equals( JDBCType.TIMESTAMP_WITH_TIMEZONE ) 
) {
    Instant instant = myResultSet.getObject( … , Instant.class ) ;
}

奇怪的是,JDBC 4.2 规范不需要支持两个最常用的 java.time 类和 .因此,如果您的 JDBC 不支持上面看到的代码,请使用。InstantZonedDateTimeOffsetDateTime

OffsetDateTime offsetDateTime = myResultSet.getObject( … , OffsetDateTime.class ) ;

我想从带有JDBC的Oracle DB表中获取DATETIME列。

根据此文档,Oracle 数据库中没有列数据类型。该术语似乎是Oracle的词,用于将其所有日期时间类型称为一个组。DATETIME

我没有看到你的代码的重点来检测类型和分支哪个数据类型。通常,我认为您应该在特定表和特定业务问题的上下文中显式地制作代码。也许这在某种通用框架中是有用的。如果您坚持,请继续阅读以了解各种类型,并了解Java 8及更高版本中内置的非常有用的新java.time类,这些类取代了您的问题中使用的类。

智能对象,而不是哑字符串

valueToInsert = aDate.toString();

您似乎试图以文本和对象的形式与数据库交换日期时间值。不要。String

若要与数据库交换日期时间值,请使用日期时间对象。现在在Java 8及更高版本中,这意味着java.time对象,如下所述。

各种类型系统

您可能会混淆三组与日期时间相关的数据类型:

  • 标准 SQL 类型
  • 专有类型
  • JDBC 类型

SQL 标准类型

SQL 标准定义了五种类型

  • 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(或 )
    只有时间,没有日期。如果输入中包含足够的数据,则应用时区和夏令时规则。鉴于其他数据类型,其有用性值得怀疑,如Postgres文档中所述TIMETZ

日期和时间

  • TIMESTAMP
    日期和时间,但忽略时区。传递到数据库的任何时区信息都将被忽略,并且不会对 UTC 进行调整。因此,这并不代表时间轴上的特定时刻,而是大约26-27小时内可能的时刻范围。如果时区或偏移量(a)未知或(b)不相关,例如“我们世界各地的所有工厂都在中午午餐时关闭”,请使用此选项。如果您有任何疑问,则不太可能是正确的类型TIMESTAMP WITHOUT TIME ZONE
  • TIMESTAMP WITH TIME ZONE(或 )
    与时区相关的日期和时间。请注意,此名称是一些用词不当,具体取决于实现。某些系统可能会存储给定的时区信息。在其他系统(如Postgres)中,不会存储时区信息,而是使用传递到数据库的时区信息将日期时间调整为UTC。TIMESTAMPTZ

专有

许多数据库提供自己的日期时间相关类型。专有类型差异很大。有些是应该避免的旧旧类型。有些被供应商认为提供了某些好处;您决定是否只使用标准类型。请注意:某些专有类型的名称与标准类型冲突;我正在看着你甲骨文日期

京东

Java 平台处理日期时间的内部详细信息的方式与 SQL 标准或特定数据库不同。JDBC 驱动程序的工作是调解这些差异,充当桥梁,根据需要转换类型及其实际实现的数据值。java.sql.* 包就是这座桥。

JDBC 遗留类

在 Java 8 之前,JDBC 规范为日期时间工作定义了 3 种类型。前两个是黑客,就像版本8之前一样,Java缺少任何类来表示仅日期或仅时间值。

  • java.sql.Date
    模拟仅日期,假装没有时间,没有时区。可能会令人困惑,因为这个类是围绕java.util.Date的包装器,它跟踪日期和时间。在内部,时间部分设置为零(午夜 UTC)。
  • java.sql.Time
    仅限时间,假装没有日期,也没有时区。也可能令人困惑,因为这个类也是一个围绕java.util.Date的薄包装器,它跟踪日期和时间。在内部,日期设置为零(1970 年 1 月 1 日)。
  • java.sql.时间戳
    日期和时间,但没有时区。这也是围绕java.util.Date的薄包装。

因此,这回答了您关于在 ResultSet 接口中没有“getDateTime”方法的问题。该接口为 JDBC 中定义的三种桥接数据类型提供了 getter 方法

请注意,第一个缺少时区或与 UTC 的偏移量的任何概念。最后一个,总是在UTC中,尽管它的方法告诉你什么。java.sql.TimestamptoString

JDBC 现代类

您应该避免上面列出的那些设计不佳的 JDBC 类。它们被java.time类型所取代。

  • 请使用 而不是 ,请使用 。适合 SQL 标准类型。java.sql.DateLocalDateDATE
  • 请使用 而不是 ,请使用 。适合 SQL 标准类型。java.sql.TimeLocalTimeTIME WITHOUT TIME ZONE
  • 请使用 而不是 ,请使用 。适合 SQL 标准类型。java.sql.TimestampInstantTIMESTAMP WITH TIME ZONE

从 JDBC 4.2 及更高版本开始,您可以直接与数据库交换 java.time 对象。使用/方法。setObjectgetObject

插入/更新。

myPreparedStatement.setObject( … , instant ) ;

检索。

Instant instant = myResultSet.getObject( … , Instant.class ) ;

Instant 类以 UTC 格式表示时间轴上的某个时刻,分辨率为纳秒(最多九 (9) 位小数)。

调整时区

如果要通过特定区域(时区)的人员使用的挂钟时间而不是 UTC 来查看的某个时刻,请通过应用 来调整以获取对象。InstantZoneIdZonedDateTime

ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;

生成的对象是相同的时刻,时间轴上的相同同时点。新的一天在东部更早出现,因此日期和时间会有所不同。例如,在新西兰午夜后几分钟,在 UTC 中仍然是“昨天”。ZonedDateTime

您可以将另一个时区应用于 或 通过其他某个地区的人们使用的另一个挂钟时间查看相同的同时时刻。InstantZonedDateTime

ZoneId zMontréal = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdtMontréal = zdtAuckland.withZoneSameInstant( zMontréal ) ;  // Or, for the same effect: instant.atZone( zMontréal ) 

所以现在我们有三个对象(,,)都表示时间轴上的同一时刻,同一点。instantzdtAucklandzMontréal

检测类型

回到问题中关于检测数据库数据类型的代码:(a)不是我的专业领域,(b)我会避免上面提到的这个问题,以及(c)如果你坚持这一点,请注意,从Java 8及更高版本开始,该类已经过时了。该类现在被实现新接口 SQLTypeJDBCType 的正确 Java 枚举所取代。请参阅相关问题的答案。java.sql.Types

此更改在 JDBC 维护版本 4.2 第 3 节和第 4 节中列出。引用:

添加 java.sql.JDBCType Enum

用于标识泛型 SQL 类型的枚举,称为 JDBC 类型。目的是使用 JDBCType 代替 Types.java 中定义的常量。

枚举具有与旧类相同的值,但现在提供类型安全

关于语法的说明:在现代Java中,您可以在对象上使用。因此,无需使用您的问题中看到的级联 if-then 语句。一个问题是,由于某些晦涩的技术原因,在切换时必须使用枚举对象的名称,因此您必须打开而不是限定。使用语句。switchEnumswitchTIMESTAMP_WITH_TIMEZONEJDBCType.TIMESTAMP_WITH_TIMEZONEstatic 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 ;

}

enter image description here


关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧日期时间类,如java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。

要了解更多信息,请参阅 Oracle 教程。搜索 Stack Overflow 以获取许多示例和解释。规格是JSR 310

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,不需要类。java.sql.*

从哪里获取 java.time 类?

ThreeTen-Extra 项目通过其他类扩展了 java.time。这个项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuarter


更新:Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。这一部分作为历史保持不变。

城大时间

在Java 8(java.time.*包)之前,与java(java.util.Date & Calendar,java.text.SimpleDateFormat)捆绑在一起的日期时间类是出了名的麻烦,令人困惑和有缺陷。

更好的做法是采用 JDBC 驱动程序提供的内容,并从中创建 Joda-Time 对象,或者在 Java 8 中创建 java.time.* 包。最终,您应该会看到新的 JDBC 驱动程序,这些驱动程序会自动使用新的 java.time.* 类。在此之前,一些方法已被添加到诸如java.sql.Timestamp之类的类中,以插入java.time,例如toInstant和.fromInstant

字符串

至于问题的后半部分,呈现一个字符串...应使用格式化程序对象来生成字符串值。

老式的方式是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

推荐