什么是JDBC-mysql驱动程序设置,以便在UTC中理解DATETIME和TIMESTAMP?

时间:2016-11-18 06:19:08

标签: java mysql datetime jdbc

过去曾经被mysql timezone和Daylight Savings“从地狱一小时”问题烧掉,我决定我的下一个应用程序会以UTC时区存储所有内容,并且只使用UTC时间与数据库进行交互(甚至不是紧密相关的GMT) )。

我很快遇到了一些神秘的错误。把头发拉了一会儿之后,我想出了这个测试代码:

try(Connection conn = dao.getDataSource().getConnection();
    Statement stmt = conn.createStatement()) {

    Instant now = Instant.now();

    stmt.execute("set time_zone = '+00:00'");

    stmt.execute("create temporary table some_times("
            + " dt datetime,"
            + " ts timestamp,"
            + " dt_string datetime,"
            + " ts_string timestamp,"
            + " dt_epoch datetime,"
            + " ts_epoch timestamp,"
            + " dt_auto datetime default current_timestamp(),"
            + " ts_auto timestamp default current_timestamp(),"
            + " dtc char(19) generated always as (cast(dt as character)),"
            + " tsc char(19) generated always as (cast(ts as character)),"
            + " dt_autoc char(19) generated always as (cast(dt_auto as character)),"
            + " ts_autoc char(19) generated always as (cast(ts_auto as character))"
            + ")");

    PreparedStatement ps = conn.prepareStatement("insert into some_times "
            + "(dt, ts, dt_string, ts_string, dt_epoch, ts_epoch) values (?,?,?,?,from_unixtime(?),from_unixtime(?))");

    DateTimeFormatter dbFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("UTC"));

    ps.setTimestamp(1, new Timestamp(now.toEpochMilli()));
    ps.setTimestamp(2, new Timestamp(now.toEpochMilli()));
    ps.setString(3, dbFormat.format(now));
    ps.setString(4, dbFormat.format(now));
    ps.setLong(5, now.getEpochSecond());
    ps.setLong(6,  now.getEpochSecond());
    ps.executeUpdate();             

    ResultSet rs = stmt.executeQuery("select * from some_times");
    ResultSetMetaData md = rs.getMetaData();

    while(rs.next()) {
        for(int c=1; c <= md.getColumnCount(); ++c) {
            Instant inst1 = Instant.ofEpochMilli(rs.getTimestamp(c).getTime());
            Instant inst2 = Instant.from(dbFormat.parse(rs.getString(c).replaceAll("\\.0$", "")));
            System.out.println(inst1.getEpochSecond() - now.getEpochSecond());
            System.out.println(inst2.getEpochSecond() - now.getEpochSecond());
        }
    }
}

注意会话时区如何设置为UTC,Java代码中的所有内容都是时区感知并强制为UTC。整个环境中唯一不是UTC的是JVM的默认时区。

我希望输出是一堆0,但我得到了这个

0
-28800
0
-28800
28800
0
28800
0
28800
0
28800
0
28800
0
28800
0
0
-28800
0
-28800
28800
0
28800
0

每行输出只是减去从检索时间中存储的时间。每行的结果应为0

似乎JDBC驱动程序正在执行不适当的时区转换。对于完全以UTC格式进行交互的应用程序,尽管它运行在不是UTC的VM上,有没有办法完全禁用TZ转换?

即。可以进行此测试以输出全零行吗?

更新

使用useLegacyDatetimeCode=falsecacheDefaultTimezone=false没有任何区别)更改输出但仍未修复:

0
-28800
0
-28800
0
-28800
0
-28800
0
-28800
0
-28800
0
-28800
0
-28800
0
0
0
0
0
0
0
0

UPDATE2

检查控制台(在更改测试以创建永久表之后),我看到所有值都正确存储:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 27148
Server version: 5.7.12-log MySQL Community Server (GPL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> set time_zone = '-00:00';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM some_times \G
*************************** 1. row ***************************
       dt: 2016-11-18 15:39:51
       ts: 2016-11-18 15:39:51
dt_string: 2016-11-18 15:39:51
ts_string: 2016-11-18 15:39:51
 dt_epoch: 2016-11-18 15:39:51
 ts_epoch: 2016-11-18 15:39:51
  dt_auto: 2016-11-18 15:39:51
  ts_auto: 2016-11-18 15:39:51
      dtc: 2016-11-18 15:39:51
      tsc: 2016-11-18 15:39:51
 dt_autoc: 2016-11-18 15:39:51
 ts_autoc: 2016-11-18 15:39:51
1 row in set (0.00 sec)

mysql>

1 个答案:

答案 0 :(得分:2)

解决方案是使用angular.module('myApp', []).controller('myCtrl', function($scope, $http){ //$scope.tok = ''; $http({ method : "POST", url : "http://server.com/api/authenticate", data: '{"username":"username","password":"password","rememberMe":true}', headers:{"Content-Type": "application/json;charset=UTF-8", } }).then( function mySuccess(response){ $scope.token = response.data.id_token; $http({ method: "GET", url: "http://server.com/api/account", data: '', headers:{"Authorization": "Bearer " + $scope.token, "Content-Type": "application/json;charset=UTF-8"} }).then( function mySuccess(response){ console.log(response); }, function myError(response){ console.log(response); }); }); }, function myError(response){ console.log(response); }); 设置JDBC连接参数noDatetimeStringSync=true。作为奖励,我还发现useLegacyDatetimeCode=false减少了sessionVariables=time_zone='-00:00'对每个新连接的明确需求。

有一些&#34;智能&#34;时区转换代码,当检测到该列为set time_zone列时,会在ResultSet.getString()方法内深入激活。

唉,这个智能代码有一个错误:TIMESTAMPTimeUtil.fastTimestampCreate(TimeZone tz, int year, int month, int day, int hour, int minute, int seconds, int secondsPart)错误地标记为JVM的默认时区,即使Timestamp参数设置为其他内容也是如此:

tz

返回final static Timestamp fastTimestampCreate(TimeZone tz, int year, int month, int day, int hour, int minute, int seconds, int secondsPart) { Calendar cal = (tz == null) ? new GregorianCalendar() : new GregorianCalendar(tz); cal.clear(); // why-oh-why is this different than java.util.date, in the year part, but it still keeps the silly '0' for the start month???? cal.set(year, month - 1, day, hour, minute, seconds); long tsAsMillis = cal.getTimeInMillis(); Timestamp ts = new Timestamp(tsAsMillis); ts.setNanos(secondsPart); return ts; } 将完全有效,除非在调用链中进一步向上,它将使用裸ts方法转换回字符串,这会将toString()呈现为表示时钟将在JVM默认时区中显示的字符串,而不是UTC时间的字符串表示形式。在ts

ResultSetImpl.getStringInternal(int columnIndex, boolean checkDateTypes)

设置 case Types.TIMESTAMP: Timestamp ts = getTimestampFromString(columnIndex, null, stringVal, this.getDefaultTimeZone(), false); if (ts == null) { this.wasNullFlag = true; return null; } this.wasNullFlag = false; return ts.toString(); 会禁用整个解析/解析混乱,只返回从数据库收到的字符串值。

测试输出:

noDatetimeStringSync=true

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 仍然很重要,因为它会改变useLegacyDatetimeCode=false使用数据库服务器的TZ的行为。

虽然追逐这一点我也发现getDefaultTimeZone()的文档不正确,虽然它没有区别:文档说[这是遗留日期时间代码的一部分,因此属性有效只有在&#34; useLegacyDatetimeCode = true。&#34; ]时,如果错误,请参阅useJDBCCompliantTimezoneShift