统一javaweb项目和mysql数据库时间UTC时间方法及原理
文章目录
前言
由于在时间处理上,北京时间和世界时间相差8小时,如果时间处理不当,会造成程序混乱。所以必需统一时间,建议使用UTC时间,当然使用北京时间也没毛病。
UTC时间与 GMT时间
- UTC:协调世界时,又称世界统一时间,世界标准时间,国际协调时间,简称UTC。
- GMT:经过格林威治老天文台本初子午线的时间便被称为世界时,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。
我们可以认为格林威治时间就是时间协调时间(GMT=UTC)
,格林威治时间和UTC时间均用秒数来计算的。
本地时间=UTC + 时区差
例如:
北京时间:2019-03-22 12:20:00
UTC时间:2019-03-22 12:20:00 UTC+8:00
GMT时间:2019-03-22 4:20:00 UTC+0:00
时间戳和时区
- 时间戳:
所有的linux系统文件系统底层存储的都是UTC时间,也就是说都是自1970年0时0分0秒以来的UTC标准时间的秒数。
无论系统配置是什么时区,显示如何不同,底层存储都是一致的,这个时间叫时间戳。
时间戳不分东西南北、在地球的每一个角落都是相同的。 - 时区:
时间戳在地球的每一个角落都是相同的,但是在相同的时间点会有不同的表达方式,所以有了另外一个时间概念,叫时区。例如北京时间叫东八区
mysql时间字段解读
4种日期类型
参考官方文档
mysql中有4种日期类型
- date
只有年月日,没有时分秒,格式如下:YYYY-MM-DD
支持时间范围是1000-01-01’ to '9999-12-31
很少用 - time
只有时分秒,格式如下: HH:MM:SS
很少用 - datetime
年月日时分秒,格式如下:YYYY-MM-DD HH:MM:SS
支持时间范围是1000-01-01’ to '9999-12-31 - timestamp
时间戳,常用,返回的格式是YYYY-MM-DD HH:MM:SS。如果想获得数字值(UTC时间),在列后台添加+0
比如
select now();
结果
+---------------------+
| now() |
+---------------------+
| 2019-03-22 10:21:50 |
+---------------------+
1 row in set
select now()+0;
结果
mysql> select now()+0;
+----------------+
| now()+0 |
+----------------+
| 20190322102206 |
+----------------+
1 row in set
比如datetime和timestamp区别
- 两者的存储方式不一样
对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储(换句话说,将当前时区时间转化成自1970年0时0分0秒以来的UTC标准时间的秒数)。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。
sql语句当前时区时间,不能插入UTC时间(比如2019-03-22T02:00:00Z)。这个区别对开发者和用户是透明的,可以忽略
- 两者所能存储的时间范围不一样
timestamp所能存储的时间范围为:’1970-01-01 00:00:01.000000’ 到 ‘2038-01-19 03:14:07.999999’。
datetime所能存储的时间范围为:’1000-01-01 00:00:00.000000’ 到 ‘9999-12-31 23:59:59.999999’。
没太大区别,可以忽略这个区别
- 自动初始化和更新
自动初始化指的是如果对该字段(譬如上例中的hiredate字段)没有显性赋值,则自动设置为当前系统时间。
自动更新指的是如果修改了其它字段,则该字段的值将自动更新为当前系统时间。
timestamp有自动初始化和更新,当你update某条记录的时候,该列值会自动更新,这是和datatime最大的区别。
其实2者区别不是特别的大,但timestamp比datetime常用。
查看和修改时区
查看时区
show variables like "%time_zone%";
返回
mysql> show variables like "%time_zone%";
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set
说明time_zone和system_time_zone使用system的时区,而在中国,时区基本上都是东八区,在linux上查看时区date -R
java里时间类
获得系统时间类
参考java8 LocalDate LocalDateTime等时间类介绍
下面的方法是获得当前时区时间
- LocalDate:LocalDate只提供日期不提供时间信息。它是不可变类且线程安全的。
获得当前日期:LocalDate.now(); - LocalTime:LocalTime只提供时间而不提供日期信息,它是不可变类且线程安全的。
获得当前时间:LocalTime.now(); - LocalDateTime:LocalDateTime提供时间和日期的信息,它是不可变类且线程安全的
获得当前日期和时间:LocalDateTime.now(); - Year:Year提供年的信息,它是不可变类且线程安全的。
- Duration:Duration是用来计算两个给定的日期之间包含多少秒,多少毫秒,它是不可变类且线程安全的
- Period: Period是用来计算两个给定的日期之间包含多少天,多少月或者多少年,它是不可变类且线程安全的
LocalDate localDate = LocalDate.now();
Period period = Period.between(localDate, localDate.plus(2, ChronoUnit.DAYS));
System.out.println(period.getDays());
获得UTC时间
使用Instant,这个类只有获得UTC时间。
//获得当前时间
Instant instant = Instant.now();
// 以ISO-8601输出
System.out.println(instant);
//结果:
2019-03-20T16:22:52.966Z
//解析时间
从字符串类型中创建Instant类型的时间
instant = Instant.parse("2019-03-20T16:22:52.966Z");
mysql获得当前时间的特有关键字
CURRENT_TIMESTAMP:获得当前时区时间,
UTC_TIMESTAMP:获得当前UTC时间
如果对时间不统一,会造成数据库里时间有的是UTC时间,有的是当前时区的时间。
在javaweb开发时我们经常遇到
Tomcat java使用UTC时区进行处理业务逻辑。从而导致Mysql数据库中lastModifyTime值查询后转到Java Bean,值少了8个小时。
修改方法有很多种
- 保持两个系统的时区一致;由于Tomcat使用UTC时间,可以在Mysql中使用UTC_TIMESTAMP,而不是CURRENT_TIMESTAMP,这样时区就都是UTC了。
lastModityTime不使用Mysql自动更新方式,而由Java Bean赋值方式。这样是最合理的。
mysql时间类型和java时间类对应关系
MySQL 类型 | java类型 |
---|---|
datetime | java.sql.Timestamp |
date | java.sql.Date 或java.time.LocalDate |
timestamp | java.sql.Timestamp或java.time. LocalDateTime |
time | java.sql.Time 或java.time.LocalTime |
通过org.apache.ibatis.type包下的类型解析器可得
mybatis处理java时间类和mysql时间类型方式
在org.apache.ibatis.type包下默认设置很多种类型,用于映射这映射关系。
比如挺好感觉Intant的类映射InstantTypeHandler类,类将java.time.intant转换成java.sql.timestamp
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
@UsesJava8
public class InstantTypeHandler extends BaseTypeHandler<Instant> {
//将Instant转换成Timestamp。注意将instant表示的UTC时间转换成了当前时区的时间
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, Timestamp.from(parameter));
}
//获得的数据又转换成Instant时间类
private static Instant getInstant(Timestamp timestamp) {
if (timestamp != null) {
return timestamp.toInstant();
}
return null;
}
}
LocalDateTimeTypeHandler类
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import org.apache.ibatis.lang.UsesJava8;
/**
* @since 3.4.5
* @author Tomas Rohovsky
*/
@UsesJava8
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
throws SQLException {
ps.setTimestamp(i, Timestamp.valueOf(parameter));
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
Timestamp timestamp = rs.getTimestamp(columnName);
return getLocalDateTime(timestamp);
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Timestamp timestamp = rs.getTimestamp(columnIndex);
return getLocalDateTime(timestamp);
}
@Override
public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Timestamp timestamp = cs.getTimestamp(columnIndex);
return getLocalDateTime(timestamp);
}
private static LocalDateTime getLocalDateTime(Timestamp timestamp) {
if (timestamp != null) {
return timestamp.toLocalDateTime();
}
return null;
}
}
其它类省略
JDBC连接数据库设置时区
使用JDBC连接数据库里5.7版本使用com.mysql.cj.jdbc.Driver 驱动包。在连接连接时要统一时区,否则容易造成混乱。
下面是设置方法
- UTC时间:serverTimezone=UTC
- 北京时间:
//北京时间东八区
serverTimezone=GMT%2B8
//或者使用上海时间
serverTimezone=Asia/Shanghai
完整例子
UTC时间
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=true
北京时间:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=true
总结
以springboot框架开发javaweb项目为便,统一时间设置为UTC时间
mysql数据库里
字段指定了datetime或timestamp都一样,推荐使用timestamp
timestamp有自动初始化和更新,当你update某条记录的时候,该列值会自动更新,这是和datatime最大的区别。
javaweb
下面的2种情况其实都会使用到,在混合使用的情况
直接写sql情况下
mybatis的映射mapper文件的sql里使用UTC_TIMESTAMP代替CURRENT_TIMESTAMP。比如
<insert id="save" useGeneratedKeys="true" keyProperty="uid">
insert into testTable ( create_a) values (UTC_TIMESTAMP )
</insert>
使用javabean传递时间
在连接mysql数据库时统一时区为UTC
jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=true
其它
有人说要统一服务器时间,配置如下
@SpringBootApplication
public class Application {
@PostConstruct
void started() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
但我没有使用也可以,先记录下吧
经过上面2种处理后,数据库时间就统一为UTC时间了,当然,这里涉及了mybatis的typeHandler处理,还是要学习的。
参考SpringBoot 统一时区的方案