# 為什么存入mysql數據庫中的timestamp,晚了13個小時
## 查看數據庫時區
```
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類型做了本地時區和數據庫時區差值計算的