统一javaweb项目和mysql数据库时间UTC时间方法及原理

统一javaweb项目和mysql数据库时间UTC时间方法及原理

前言

由于在时间处理上,北京时间和世界时间相差8小时,如果时间处理不当,会造成程序混乱。所以必需统一时间,建议使用UTC时间,当然使用北京时间也没毛病。

UTC时间与 GMT时间

  1. UTC:协调世界时,又称世界统一时间,世界标准时间,国际协调时间,简称UTC。
  2. 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

时间戳和时区

  1. 时间戳
    所有的linux系统文件系统底层存储的都是UTC时间,也就是说都是自1970年0时0分0秒以来的UTC标准时间的秒数。
    无论系统配置是什么时区,显示如何不同,底层存储都是一致的,这个时间叫时间戳。
    时间戳不分东西南北、在地球的每一个角落都是相同的。
  2. 时区
    时间戳在地球的每一个角落都是相同的,但是在相同的时间点会有不同的表达方式,所以有了另外一个时间概念,叫时区。例如北京时间叫东八区

mysql时间字段解读

4种日期类型

参考官方文档
mysql中有4种日期类型

  1. date
    只有年月日,没有时分秒,格式如下:YYYY-MM-DD
    支持时间范围是1000-01-01’ to '9999-12-31
    很少用
  2. time
    只有时分秒,格式如下: HH:MM:SS
    很少用
  3. datetime
    年月日时分秒,格式如下:YYYY-MM-DD HH:MM:SS
    支持时间范围是1000-01-01’ to '9999-12-31
  4. 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

比如datetimetimestamp区别

  1. 两者的存储方式不一样
    对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储(换句话说,将当前时区时间转化成自1970年0时0分0秒以来的UTC标准时间的秒数)。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。

sql语句当前时区时间,不能插入UTC时间(比如2019-03-22T02:00:00Z)。这个区别对开发者和用户是透明的,可以忽略

  1. 两者所能存储的时间范围不一样
    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’。

没太大区别,可以忽略这个区别

  1. 自动初始化和更新
    自动初始化指的是如果对该字段(譬如上例中的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等时间类介绍
下面的方法是获得当前时区时间

  1. LocalDate:LocalDate只提供日期不提供时间信息。它是不可变类且线程安全的。
    获得当前日期:LocalDate.now();
  2. LocalTime:LocalTime只提供时间而不提供日期信息,它是不可变类且线程安全的。
    获得当前时间:LocalTime.now();
  3. LocalDateTime:LocalDateTime提供时间和日期的信息,它是不可变类且线程安全的
    获得当前日期和时间:LocalDateTime.now();
  4. Year:Year提供年的信息,它是不可变类且线程安全的。
  5. Duration:Duration是用来计算两个给定的日期之间包含多少秒,多少毫秒,它是不可变类且线程安全的
  6. 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个小时。
修改方法有很多种

  1. 保持两个系统的时区一致;由于Tomcat使用UTC时间,可以在Mysql中使用UTC_TIMESTAMP,而不是CURRENT_TIMESTAMP,这样时区就都是UTC了。
  2. lastModityTime不使用Mysql自动更新方式,而由Java Bean赋值方式。这样是最合理的。

mysql时间类型和java时间类对应关系

MySQL 类型java类型
datetimejava.sql.Timestamp
datejava.sql.Date 或java.time.LocalDate
timestampjava.sql.Timestamp或java.time. LocalDateTime
timejava.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 驱动包。在连接连接时要统一时区,否则容易造成混乱。
下面是设置方法

  1. UTC时间:serverTimezone=UTC
  2. 北京时间:
//北京时间东八区
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 统一时区的方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值