JDBC 在将日期插入 Oracle DB 之前是否调整日期?我该如何防止这种情况?

Does JDBC adjust the Date before it inserts it into Oracle DB? How do I prevent this?

假设我们有以下创建日期的代码:

SimpleDateFormat sdf = new SimpleDateFormat( "dd/MM/yyyy" );
sdf.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); // we know the date being parsed is UTC
Date bizDate = sdf.parse( "12/12/2015" ); // milliseconds: 1449878400000
log.info( "ms: {}", bizDate.getTime() );
log.info( "date: {}", bizDate );
... // save to db

如果该代码在 UTC 的 JVM 上运行在 UTC 的 Oracle 数据库上,我得到:

JVM 参数:-Duser.timezone=UTC

millisecond: 1449878400000
date: Sat Dec 12 00:00:00 UTC 2015
in oracle db: 2015-Dec-12 00:00:00 // zero time

对于未设置为 UTC(例如 SGT)的 JVM,我得到:

JVM 参数:none(默认时区为 SGT 或 UTC+8:00)

millisecond: 1449878400000
date: Sat Dec 12 08:00:00 SGT 2015
in oracle db: 2015-Dec-12 08:00:00 // plus 8 hours

请注意,它们的毫秒数相同,但它们在数据库中的插入方式不同。

我的问题是:

  1. JDBC 标准是否说它在插入之前调整 Date 对象?请注明出处。

  2. 如果 JVM 的时区未设置为 UTC,如果 JDBC 确实在将 Date 对象插入数据库之前调整了它,为什么要这样设计?我觉得这让事情变得更加混乱。我期待它会按原样插入。想象一下,如果您使用毫秒创建一个日期(例如 new Date( 1449878400000L ) )并且它将以不同的方式存储并且您没有关于 JVM 时区的信息,您的代码将在 运行 中。或者想象您的代码将是 运行 在设置为不同时区的多个 JVM 上。

  3. 当 JVM 的时区设置为 UTC 以外的任何时区时,如何防止 JDBC 调整日期?我正在使用 ibatis,我可能无法直接访问 PreparedStatements

我已将 SimpleDateFormat 的时区设置为 UTC,因为我想将解析的日期视为 UTC(或根据需要作为其他时区)。如果没有这样的要求,那将不是问题。现在看来我需要调整日期以反转 JDBC 在插入它之前正在做的事情。

问题是 Java Date 对象不存储时区。该值在 UTC 中是 always,并在给定的时区进行解析和格式化,通常是 JVM 的默认时区。

Oracle DATE 列也没有时区存储,但应该表示用户看到的日期。在 99.99% 的情况下,这意味着 JVM 默认时区中的日期。

因此,JDBC 驱动程序采用 UTC 中的 Timestamp / Date 值,将其转换为默认时区,并将其保存到数据库中。

您正在使用 PreparedStatement.setTimestamp(int parameterIndex, Timestamp x) method. To control the time zone, use the PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) 方法。引用 javadoc:

Sets the designated parameter to the given java.sql.Timestamp value, using the given Calendar object. The driver uses the Calendar object to construct an SQL TIMESTAMP value, which the driver then sends to the database. With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone. If no Calendar object is specified, the driver uses the default timezone, which is that of the virtual machine running the application.