天天看點

java統一項目用什麼_Java項目統一UTC時間方案

引言

近期團隊的個别項目在進行架構更新後,部分時間值存在8小時誤差,原因是錯誤的将資料庫中的時間資料了解成了UTC時間(舊版本認為是中原標準時間)

考慮到未來項目對于時間了解的一緻性,我決定将項目統一為使用UTC時間,經調研,形成本文

mysql資料庫時區及時間時間類型說明

資料庫時區

mysql資料庫擁有時區設定,預設使用系統時區

可通過如下語句查詢目前時區

show variables like '%time_zone%';

下圖為我個人機器上mysql資料庫時區設定:

java統一項目用什麼_Java項目統一UTC時間方案

項目線上資料庫時區設定如下:

java統一項目用什麼_Java項目統一UTC時間方案

可見資料庫使用系統時間CST——China Standard Time UTC+8:00 中國沿海時間(中原標準時間)

時間類型說明

datetime

實際格式儲存(Just stores what you have stored and retrieves the same thing which you have stored.)

與時區無關(It has nothing to deal with the TIMEZONE and Conversion.)

timestamp

值以UTC毫秒數儲存( it stores the number of milliseconds)

存儲及檢索時根據目前時區設定,對時間數值做轉換

由于timestamp與時區相關,且線上資料庫時區設定為中原標準時間(即UTC+8:00)。是以,當資料庫中使用了timestamp列,若使用不當,統一UTC格式時間改造将很可能會引入錯誤! 後面詳述理由

統一UTC時間改造方案簡述

統一時區設定

項目新架構中通過UTCTimeZoneConfiguration類型,在項目初始化時設定目前程序的預設時區

@Configuration

public class UTCTimeZoneConfiguration implements ServletContextListener{

public void contextInitialized(ServletContextEvent event) {

System.setProperty("user.timezone", "UTC");

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

}

public void contextDestroyed(ServletContextEvent event) {}

}

時間類型Joda DateTime的使用方式

日期時間類型可以使用 java.util.Date,但推薦使用更為友善的joda DateTime,本節介紹joda DateTime 序列化/反序列化使用方式

Joda DateTime 類型用于定義接口輸入輸出參數,需進行序列化/反序列化操作。與原生的Date類型不同,DateTime需要做一點額外處理

1、Model類型的日期字段使用類型DateTime替代Date

執行個體代碼如下

public class Entity {

@JsonSerialize(using = UTCDateTimeSerializer.class)

@JsonDeserialize(using = UTCDateTimeDeserializer.class)

private DateTime dateTime;

public DateTime getDateTime() {

return dateTime;

}

public void setDateTime(DateTime dateTime) {

this.dateTime = dateTime;

}

}

其中UTCDateTimeSerializer與UTCDateTimeDeserializer類的實作見附錄

2、Get請求接受時間參數

此時,一種有效的處理方式是使用字元串接受日期參數,如下:

@RequestMapping(value = "/xxx", method = RequestMethod.GET)

public CommonResponse getXxx(@RequestParam(value = "beginTime") String beginTimeText,

@RequestParam(value = "endTime") String endTimeText) {

DateTime beginTime = DateTime.parse(beginTimeText).withZone(DateTimeZone.UTC);

DateTime endTime = DateTime.parse(endTimeText).withZone(DateTimeZone.UTC);

...

}

Dao時間操作——針對資料庫列為datetime的場景

以Joda DateTime類型舉例說明使用方法,某Dao類型中存在的兩個方法如下:

public void update(int id, DateTime dateTime) {

String sql = "UPDATE " + TABLE_NAME + " SET datetime = ? WHERE id = ?";

jdbcTemplate.update(sql, new Timestamp(dateTime.getMillis()), id);

}

public DateTime getDateTime(int id) {

String sql = "SELECT datetime FROM " + TABLE_NAME + " WHERE id = ?";

List dateTimeList = jdbcTemplate.query(sql, new Object[] {id}, new RowMapper() {

@Override

public DateTime mapRow(ResultSet rs, int rowNum) throws SQLException {

return new DateTime(rs.getTimestamp("datetime").getTime());

}

});

return dateTimeList.size() > 0 ? dateTimeList.get(0) : null;

}

插入或更新資料,傳遞的時間參數請使用 new Timestamp(dateTime.getMillis())

讀取時間參數,使用new DateTime(rs.getTimestamp("datetime").getTime())

Dao時間操作——針對資料庫列為timestamp的場景

資料庫timestamp類型适合用來記錄資料的最後修改時間

其他場景建議使用datetime或者int

方案一更改會話時區為UTC時間

對timestamp列的操作與datetime列的操作不做區分,此時需要設定資料連接配接會話的時區,預設為中原標準時間,需要設定為UTC時間,通過如下語句設定

set time_zone = '+0:00';

實際項目中使用資料庫連接配接池,建立datasource後使用如下方式設定時區,将對所有連接配接生效

dataSource.setInitSQL("set time_zone = '+0:00'");

經此操作後,時區統一為UTC時間,Dao中時間操作,無需對timestamp做特殊處理

方案二不更改會話時區

由于不更改時區,timestamp類型資料的使用存在一定限制

1、如何更新timestamp資料

對于資料庫表中的timestamp列,其值的更新應當由資料庫自行維護,在create table時設定,如下:

CREATE TABLE t1 (

ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

);

可簡寫如下

CREATE TABLE t1 (

ts TIMESTAMP

);

不允許程式自主更新timstamp列資料

線上資料庫時區為中原標準時間,其接受到的日期資料被視為中原標準時間,而上層程式業務邏輯統一使用UTC時間,時區不統一。是以避免資料庫記錄的日期資料了解不一緻,不允許程式通過寫操作sql語句更新timestamp列

下圖資料為本人實測資料,timestamp列由程式進行更新,update_time列則由資料庫自動更新

java統一項目用什麼_Java項目統一UTC時間方案

前者顯示的是UTC時間,看似合理,實則錯誤,資料庫内部存儲時間為UTC-8:00

update_time符合資料庫時區設定,傳回中原標準時間,内部實際存儲UTC時間

2、如何讀取timestamp資料

為避免從資料庫中擷取時區相關時間(中原標準時間),強制使用UTC時間,使用函數UNIX_TIMESTAMP擷取1970年至今秒數,轉換成DateTime時乘以1000轉變為毫秒

public DateTime getTimestamp(int id) {

String sql = "SELECT UNIX_TIMESTAMP(update_time) as unix_timestamp FROM " + TABLE_NAME + " WHERE id = ?";

List dateTimeList = jdbcTemplate.query(sql, new Object[] {id}, new RowMapper() {

@Override

public DateTime mapRow(ResultSet rs, int rowNum) throws SQLException {

return new DateTime(rs.getLong("unix_timestamp") * 1000);

}

});

return dateTimeList.size() > 0 ? dateTimeList.get(0) : null;

}

附錄

Mysql時區設定

設定全局時區,需要管理者權限

使用本機系統時區

SET GLOBAL time_zone = SYSTEM;

使用UTC時間

SET GLOBAL time_zone = '+0:00';

使用中原標準時間

SET GLOBAL time_zone = '+8:00';

設定目前連接配接會話時區

set time_zone = '+0:00';

UTCDateTimeSerializer與UTCDateTimeDeserializer

UTCDateTimeSerializer 完成DateTime對象到UTC時間字元串的轉換,格式為:yyyy-MM-ddTHH:mm:ssZ

UTCDateTimeDeserializer 完成時間字元串到DateTime對象的轉換,轉換為UTC時區

具體實作如下:

public class UTCDateTimeSerializer extends JsonSerializer {

@Override

public void serialize(DateTime dateTime,

JsonGenerator jsonGenerator,

SerializerProvider provider) throws IOException {

String dateTimeAsString = dateTime.withZone(DateTimeZone.UTC).toString(BecConstant.DATETIME_FORMAT);

jsonGenerator.writeString(dateTimeAsString);

}

}

public class UTCDateTimeDeserializer extends JsonDeserializer {

@Override

public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)

throws IOException {

JsonToken currentToken = jsonParser.getCurrentToken();

if (currentToken == JsonToken.VALUE_STRING) {

String dateTimeAsString = jsonParser.getText().trim();

return DateTime.parse(dateTimeAsString).withZone(DateTimeZone.UTC);

}

return null;

}

}