1. 程式人生 > 實用技巧 >為什麼存入mysql資料庫中的timestamp,晚了13或14個小時

為什麼存入mysql資料庫中的timestamp,晚了13或14個小時

為什麼存入mysql資料庫中的timestamp,晚了13或14個小時

目錄

使用markdown,方便大家瀏覽,就又更新了一下

檢視資料庫時區

show variables like '%time_zone%';
select @@global.system_time_zone;
select @@global.time_zone;

可以得到預設資料庫時區:
system_time_zone | CST |
time_zone | SYSTEM|

CST時區:4個含義

CST可以為如下4個不同的時區的縮寫:
1,美國中部時間:Central Standard Time (USA) UT-6:00 ,又美國從“3月11日”至“11月7日”實行夏令時,美國中部時間改為 UT-05:00
2,澳大利亞中部時間:Central Standard Time (Australia) UT+9:30
3,中國標準時間:China Standard Time UT+8:00
4,古巴標準時間:Cuba Standard Time UT-4:00

PS:即中國標準時間UT+8,和美國UT-5,中間相差13個小時

檢視java程式執行的本地時區

 TimeZone.getDefault();//得到"Asia/Shanghai"

debug與原始碼分析:

1,測試發現,客戶端到java程式端的時間戳是正確的,即通過mybatis寫入資料庫之前時間戳是正確的
2,從mybatis一路跟蹤:mybatis SqlTimestampTypeHandler.setNonNullParameter()->mybatis PreparedStatement.setTimestamp-》mysql-connector preparedStatement.setTimestamp()-》preparedStatement.setTimestampInternal()-》TimeUtil.changTimestamp(),通過計算本地時區和資料庫時區差值,得到資料的時間戳,再轉成SimpleDateFormat.format yyyy-MM-dd HH:mm:ss格式的時間戳日期字串,寫入資料庫


3,問題:java執行的本地時區是"Asia/Shanghai",那mysql-connector得到的資料庫時區是什麼樣的?連線資料庫的時候,mysql-connector會獲取資料庫的時區資訊,如上資料庫時區查詢,得到SYSTEM,CST

mysql-connector獲取資料庫時區

1,CST 的時區是一個很混亂的時區,在與 MySQL 協商會話時區時,Java 會誤以為是 CST -0500,而非 CST +0800

private void configureTimezone() throws SQLException {
        String configuredTimeZoneOnServer = (String) this.serverVariables
                .get("timezone");

        if (configuredTimeZoneOnServer == null) {
            configuredTimeZoneOnServer = (String) this.serverVariables
                    .get("time_zone");

            if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
                configuredTimeZoneOnServer = (String) this.serverVariables
                        .get("system_time_zone");//得到CST,mysql-connector以為的CST是美國的CST-5:00
            }
        }
        ...
}

2,TimeZone.getTimeZone(canonicalTimezone)得到CST,mysql-connector以為的CST是美國的CST-5:00,{"CST", "America/Chicago"}
3,mysql-connector ZoneInfoFile class時區簡寫和時區對應關係

{{"ACT", "Australia/Darwin"}, 
 {"AET", "Australia/Sydney"}, 
 {"AGT", "America/Argentina/Buenos_Aires"},
 {"ART", "Africa/Cairo"},
 {"AST", "America/Anchorage"}, 
 {"BET", "America/Sao_Paulo"}, 
 {"BST", "Asia/Dhaka"}, 
 {"CAT", "Africa/Harare"}, 
 {"CNT", "America/St_Johns"},
 {"CST", "America/Chicago"}, 
 {"CTT", "Asia/Shanghai"}, 
 {"EAT", "Africa/Addis_Ababa"}, 
 {"ECT", "Europe/Paris"}, 
 {"IET", "America/Indiana/Indianapolis"}, 
 {"IST", "Asia/Kolkata"}, 
 {"JST", "Asia/Tokyo"}, 
 {"MIT", "Pacific/Apia"}, 
 {"NET", "Asia/Yerevan"}, 
 {"NST", "Pacific/Auckland"}, 
 {"PLT", "Asia/Karachi"}, 
 {"PNT", "America/Phoenix"}, 
 {"PRT", "America/Puerto_Rico"},
 {"PST", "America/Los_Angeles"}, 
 {"SST", "Pacific/Guadalcanal"}, 
 {"VST", "Asia/Ho_Chi_Minh"}};

如何解決

一,修改資料庫時區

set global time_zone = '+8:00';//設定全域性時區為東八區 
set time_zone = '+8:00'; //
flush privileges;//重新整理許可權使設定立即生效

二,新增jdbc引數:serverTimezone=GMT%2B8

db?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&useTimezone=true&serverTimezone=GMT%2B8

會有什麼問題

1,因為老資料是基於CST-5:00,得到的時間戳日期字串(yyyy-MM-dd HH:mm:ss.SSS),寫入資料庫中,改了資料庫時區或修改了JDBC的時區配置,會導致舊資料比以前慢13個小時

那舊資料怎麼辦

1,建立一個mybatis TimstampTypehandler專門處理timestamp型別,將某個時間以前的時間戳加上13個小時的時間戳間隔,即可

@MappedJdbcTypes(JdbcType.TIMESTAMP)
@MappedTypes(Timestamp.class)
public class TimestampHandler extends SqlTimestampTypeHandler {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Timestamp parameter, JdbcType jdbcType)
                    throws SQLException {
        ps.setTimestamp(i, parameter);
    }

    @Override
    public Timestamp getNullableResult(ResultSet rs, String columnName)
                    throws SQLException {
        //TimeZone tz=TimeZone.getDefault();
        //TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
        Timestamp timestampTemp=rs.getTimestamp(columnName);
        long lt=timestampTemp.getTime();
        long timestampSplit=1590249600000L;//2020-05-24 00:00:00的毫秒時間戳
        if(timestampSplit>lt){
            Timestamp timestamp=new Timestamp(lt+13*60*60*1000);
            return timestamp;
        }else{
            return timestampTemp;
        }

    }

    @Override
    public Timestamp getNullableResult(ResultSet rs, int columnIndex)
                    throws SQLException {
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
        Timestamp timestampTemp=rs.getTimestamp(columnIndex);
        long lt=timestampTemp.getTime();
        long timestampSplit=1590249600000L;//2020-05-24 00:00:00的毫秒時間戳
        if(timestampSplit>lt){
            Timestamp timestamp=new Timestamp(lt+13*60*60*1000);
            return timestamp;
        }else{
            return timestampTemp;
        }
    }

    @Override
    public Timestamp getNullableResult(CallableStatement cs, int columnIndex)
                    throws SQLException {
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
        Timestamp timestampTemp=cs.getTimestamp(columnIndex);
        long lt=timestampTemp.getTime();
        long timestampSplit=1590249600000L;//2020-05-24 00:00:00的毫秒時間戳
        if(timestampSplit>lt){
            Timestamp timestamp=new Timestamp(lt+13*60*60*1000);
            return timestamp;
        }else{
            return timestampTemp;
        }
    }
}

多人開發,timestamp時間戳使用規約

1,介面引數涉及時間,都用時間戳,精確到秒或毫秒,全專案統一
2,時間戳引數直接入庫,不要在程式碼層再做一次SimpleDateFormat.format yyyy-MM-dd HH:mm:ss.SSS轉換,這樣會附加本地時區,導致時間戳失效,mysql connector在入庫前對timestamp型別做了本地時區和資料庫時區差值計算的