JDBC准备语句的时间戳问题
JDBC准备语句的时间戳问题
我必须在数据库中存储UTC日期时间。
我已将特定时区中给定的日期时间转换为UTC,为此我按照以下代码操作。
我的输入日期时间是 "20121225 10:00:00 Z",时区是 "Asia/Calcutta"。
我的服务器/数据库(oracle)运行在相同的时区(IST)"Asia/Calcutta"。
获取特定时区的日期对象
String date = "20121225 10:00:00 Z"; String timeZoneId = "Asia/Calcutta"; TimeZone timeZone = TimeZone.getTimeZone(timeZoneId); DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z"); //这个日期对象有给定的时间和给定的时区 java.util.Date parsedDate = dateFormatLocal.parse(date + " " + timeZone.getDisplayName(false, TimeZone.SHORT)); if (timeZone.inDaylightTime(parsedDate)) { //我们需要重新解析,因为我们不知道日期是否是夏令时,直到它被解析出来... parsedDate = dateFormatLocal.parse(date + " " + timeZone.getDisplayName(true, TimeZone.SHORT)); } //赋值给java.sql.TimeStamp实例变量 obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));
存储到数据库中
if (tsSchedStartTime != null) { stmt.setTimestamp(11, tsSchedStartTime); } else { stmt.setNull(11, java.sql.Types.DATE); }
输出
数据库(oracle)存储的是相同的给定日期时间:"20121225 10:00:00
,而不是UTC时间。
我已经通过下面的SQL确认了这一点。
select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable
我的数据库服务器也在相同的时区 "Asia/Calcutta" 上运行。
它给我以下外观
Date.getTime()
不是以UTC格式显示- 或者在存储到数据库中时,时间戳具有时区影响
我在这里做错了什么?
还有一个问题:
timeStamp.toString()
是否会像java.util.date
一样以本地时区打印,而不是UTC时区?
JDBC Prepared statement Timestamp issues
JDBC中的预编译语句在处理时间戳时会出现一些问题。当调用`setTimestamp(int parameterIndex, Timestamp x)`方法时,JDBC驱动程序会使用虚拟机的时区来计算时间戳的日期和时间。这个日期和时间会存储在数据库中,如果数据库列不存储时区信息,那么关于时区的任何信息都会丢失。这意味着使用数据库的应用程序必须始终使用相同的时区或想出另一种区分时区的方案(例如在单独的列中存储时区)。
如果想要将时间戳存储在不同的时区中,可以使用`setTimestamp(int parameterIndex, Timestamp x, Calendar cal)`方法,并提供一个指定时区的`Calendar`实例。在检索值时,也必须使用相同时区的等效的getter方法(如果在数据库中使用不带时区信息的`TIMESTAMP`)。
JDBC 4.2标准中,符合要求的驱动程序应通过`get/set/updateObject`方法来支持`java.time.LocalDateTime`(和`java.time.LocalTime`)来处理`TIMESTAMP`(和`TIME`)类型。`java.time.Local*`类没有时区信息,因此不需要进行转换(尽管如果代码假设了特定的时区,可能会引入新的问题)。
如果想要在GMT时区中存储时间,可以使用以下代码:
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); stmt.setTimestamp(11, tsSchedStartTime, cal);
需要注意的是,`setTimestamp(int parameterIndex, Timestamp x)`方法中的`x`参数是要设置的实际时间戳值,驱动程序只使用`Calendar`对象来获取时区信息,而不使用其值。
另外,`java.lang.Date`(和`java.lang.Timestamp`)的`toString()`方法始终以本地时区显示时间戳。
从Java 8(JDBC 4.2)开始,推荐使用`java.time.LocalDateTime`代替(使用`getObject(..., LocalDateTime.class)`方法)。
这些解决方法可以帮助解决JDBC预编译语句中处理时间戳的问题。
JDBC Prepared statement Timestamp issues
在使用JDBC的预编译语句时,有时会遇到关于时间戳(Timestamp)的问题。问题的根源在于java.sql.Timestamp这个类本身存在一些混乱的设计,因此建议使用java.time.LocalDateTime来代替。
为什么java.sql.Timestamp存在问题呢?根据java.sql.Timestamp的JavaDoc所述,它是“java.util.Date”的一个薄包装器,允许JDBC API将其识别为SQL TIMESTAMP值。而根据java.util.Date的JavaDoc所述,“Date类旨在反映协调世界时(UTC)”。而根据ISO SQL规范,TIMESTAMP WITHOUT TIME ZONE是一个“没有时区的日期时间数据类型”。TIMESTAMP是TIMESTAMP WITHOUT TIME ZONE的简称。因此,java.sql.Timestamp反映的是UTC,而SQL TIMESTAMP是“没有时区”的。
由于java.sql.Timestamp反映的是UTC,它的方法会进行转换,这就导致了很多混乱。从SQL的角度来看,将SQL TIMESTAMP值转换为其他时区是没有意义的,因为TIMESTAMP没有时区可供转换。将42转换为华氏温度意味着什么?这是毫无意义的,因为42没有温度单位,它只是一个裸露的数字。同样地,将2020-07-22T10:38:00转换为美国洛杉矶时区也是没有意义的,因为2020-07-22T10:30:00不属于任何时区,它既不是UTC也不是GMT或其他任何时区,它只是一个裸露的日期时间。
而java.time.LocalDateTime也是一个裸露的日期时间,它没有时区,与SQL TIMESTAMP类似。它的方法没有任何时区转换,这使得它的行为更容易预测和理解。因此,不要使用java.sql.Timestamp,而应该使用java.time.LocalDateTime。
解决方法如下:
LocalDateTime ldt = rs.getObject(col, LocalDateTime.class); ps.setObject(param, ldt, JDBCType.TIMESTAMP);
以上就是关于JDBC Prepared statement Timestamp issues问题出现的原因以及解决方法的整理。通过使用java.time.LocalDateTime来替代java.sql.Timestamp,可以避免这个问题带来的困扰。