SpringBoot时间戳与MySql数据库记录相差14小时排错
项目中遇到存储的时间戳与真实时间相差14小时
的现象,以下为解决步骤.
问题
CREATE TABLE `incident` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`recovery_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;
以上为数据库建表语句,其中created_time
是插入记录时自动设置,recovery_time
需要手动进行设置.
测试时发现,created_time
为正确的北京时间,然而recovery_time
则与设置时间相差14小时.
尝试措施
jvm时区设置
//设置jvm默认时间
System.setProperty("user.timezone", "UTC");
数据库时区查询
查看数据库时区设置:
show variables like '%time_zone%';
--- 查询结果如下所示:
--- system_time_zone: CST
--- time_zone:SYSTEM
查询CST
发现其指代比较混乱,有四种含义(参考网址:https://juejin.im/post/5902e087da2f60005df05c3d):
- 美国中部时间 Central Standard Time (USA) UTC-06:00
- 澳大利亚中部时间 Central Standard Time (Australia) UTC+09:30
- 中国标准时 China Standard Time UTC+08:00
- 古巴标准时 Cuba Standard Time UTC-04:00
此处发现如果按照美国中部时间
进行推算,相差14小时
,与Bug吻合.
验证过程
MyBatis转换
代码中,时间戳使用Instant
进行存储,因此跟踪package org.apache.ibatis.type
下的InstantTypeHandler
.
@UsesJava8
public class InstantTypeHandler extends BaseTypeHandler<Instant> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, Timestamp.from(parameter));
}
//...代码shenglve
}
调试时发现parameter
为正确的UTC
时.
函数中调用Timestamp.from
将Instant
转换为Timestamp
实例,检查无误.
/**
* Sets the designated parameter to the given <code>java.sql.Timestamp</code> value.
* The driver
* converts this to an SQL <code>TIMESTAMP</code> value when it sends it to the
* database.
*
* @param parameterIndex the first parameter is 1, the second is 2, ...
* @param x the parameter value
* @exception SQLException if parameterIndex does not correspond to a parameter
* marker in the SQL statement; if a database access error occurs or
* this method is called on a closed <code>PreparedStatement</code> */
void setTimestamp(int parameterIndex, java.sql.Timestamp x)
throws SQLException;
继续跟踪setTimestamp
接口,其具体解释见代码注释.
Sql Driver转换
项目使用com.mysql.cj.jdbc
驱动,跟踪其setTimestamp
在ClientPreparedStatement
类下的具体实现(PreparedStatementWrapper
类下实现未进入).
@Override
public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException {
synchronized (checkClosed().getConnectionMutex()) {
((PreparedQuery<?>) this.query).getQueryBindings().setTimestamp(getCoreParameterIndex(parameterIndex), x);
}
}
继续跟踪上端代码中的getQueryBindings().setTimestamp()
实现(com.mysql.cj.ClientPreparedQueryBindings
).
@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength) {
if (x == null) {
setNull(parameterIndex);
} else {
x = (Timestamp) x.clone();
if (!this.session.getServerSession().getCapabilities().serverSupportsFracSecs()
|| !this.sendFractionalSeconds.getValue() && fractionalLength == 0) {
x = TimeUtil.truncateFractionalSeconds(x);
}
if (fractionalLength < 0) {
// default to 6 fractional positions
fractionalLength = 6;
}
x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs());
//注意此处时区转换
this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar,
targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone());
StringBuffer buf = new StringBuffer();
buf.append(this.tsdf.format(x));
if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) {
buf.append('.');
buf.append(TimeUtil.formatNanos(x.getNanos(), 6));
}
buf.append('\'');
setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP);
}
}
注意此处时区转换,会调用如下语句获取默认时区:
this.session.getServerSession().getDefaultTimeZone()
获取TimeZone
数据,具体如下图所示:
检查TimeZone
类中offset
含义,具体如下所示:
/**
* Gets the time zone offset, for current date, modified in case of
* daylight savings. This is the offset to add to UTC to get local time.
* <p>
* This method returns a historically correct offset if an
* underlying <code>TimeZone</code> implementation subclass
* supports historical Daylight Saving Time schedule and GMT
* offset changes.
*
* @param era the era of the given date.
* @param year the year in the given date.
* @param month the month in the given date.
* Month is 0-based. e.g., 0 for January.
* @param day the day-in-month of the given date.
* @param dayOfWeek the day-of-week of the given date.
* @param milliseconds the milliseconds in day in <em>standard</em>
* local time.
*
* @return the offset in milliseconds to add to GMT to get local time.
*
* @see Calendar#ZONE_OFFSET
* @see Calendar#DST_OFFSET
*/
public abstract int getOffset(int era, int year, int month, int day,
int dayOfWeek, int milliseconds);
offset
表示本地时间
与UTC
时的时间间隔(ms)
.
计算数值offset
,发现其表示美国中部时间
,即UTC-06:00
.
Driver
推断Session
时区为UTC-6
;Driver
将Timestamp
转换为UTC-6
的String
;MySql
认为Session
时区在UTC+8
,将String
转换为UTC+8
.
因此,最终结果相差14小时,bug
源头找到.
解决方案
参照https://juejin.im/post/5902e087da2f60005df05c3d.
mysql> set global time_zone = '+08:00';
Query OK, 0 rows affected (0.00 sec)
mysql> set time_zone = '+08:00';
Query OK, 0 rows affected (0.00 sec)
告知运维设置时区,重启MySql
服务,问题解决.
此外,作为防御措施,可以在jdbc url
中设置时区(如此设置可以不用修改MySql
配置):
jdbc:mysql://localhost:3306/table_name?useTimezone=true&serverTimezone=GMT%2B8
此时,就告知连接进行时区转换
,并且时区为UTC+8
.
PS:
如果您觉得我的文章对您有帮助,请关注我的微信公众号,谢谢!
SpringBoot时间戳与MySql数据库记录相差14小时排错的更多相关文章
- mysql数据库优化课程---14、常用的sql技巧
mysql数据库优化课程---14.常用的sql技巧 一.总结 一句话总结:其实就是sql中那些函数的使用 1.mysql中函数如何使用? 选择字段 其实就是作用域select的选择字段 3.转大写: ...
- 【入门】Spring-Boot项目配置Mysql数据库
前言 前面参照SpringBoot官网,自动生成了简单项目点击打开链接 配置数据库和代码遇到的问题 问题1:cannot load driver class :com.mysql.jdbc.Drive ...
- 通用mapper版+SpringBoot+MyBatis框架+mysql数据库的整合
转:https://blog.csdn.net/qq_35153200/article/details/79538440 开发环境: 开发工具:Intellij IDEA 2017.2.3 JDK : ...
- springboot 时间戳和 数据库时间相差14个小时
在 springboot 开发过程中遇到一个奇怪的问题,就是已经设置系统时间GMT+8, 但是时间到数据库后会减少14个小时.后来发现是 jvm 时区和数据库时区设置不一致的问题. jvm 设置的是 ...
- mysql数据库记录
ON DELETE restrict(约束):当在父表(即外键的来源表)中删除对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除. no action:意思同restrict.即如果存在从数 ...
- [PHP]全国省市区信息,mysql数据库记录
下载地址: https://files.cnblogs.com/files/wukong1688/T_Area.zip 或者也可以复制如下内容: CREATE TABLE IF NOT EXISTS ...
- mysql 时区问题导致的时间相差14小时
1.mysql 字段名称 类型 begin_time TIME begin_time=08:18:39 2.java数据库连接串 jdbc:mysql://x.x.x.x:3306/y?useUnic ...
- MySQL数据库---记录相关操作
序 表中记录的相关操作一共四种:插入,更新,删除.查询.其中使用最多,也是最难的就是查询. 记录的插入 1. 插入完整数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3… ...
- springboot后端时间到前端,相差8小时,时间格式不对
spring boot后台时间正确,返回给前台的时间不正确,和后台差8个小时 { "code": 1, "msg": "SUCCESS", ...
随机推荐
- Build 2019 彩蛋
N久没写过博客了… 最近在玩 APEX 但是手残党表示打到15级了,至今杀敌 4 人… 当快递员是越来越顺手了… 今年巨硬的 Build 大会会在 5 月 6-8 号召开 新发布的 Hololens ...
- 【JAVA】反射总结
反射是什么? 反射就是指程序在运行的时候可以知道一个类的自身信息. 自身信息有哪些:类方法.属性.成员变量.构造方法.包等 动态编译和静态编译 静态编译:在编译的时候进确定类型,如果绑定对象成功,n ...
- C++ 面向对象的三大特性和五个原则
1.三大特性: 封装:就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏.一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体.在一个对象内 ...
- 如何在.Net Core调用NodeJs
概述 Microsoft.AspNetCore.NodeServices库 实例 新建aspnet core站点 添加nuget包 建立node环境,此处示例用于扫描wifi环境 建立nodejs的程 ...
- 强制.net 程序以32位模式运行
You can modify the PE header of the executable (using the "Corflags.exe" .NET Framework SD ...
- notes for lxf(四)
类名首字母通常大写 创建实例 类名 +() __init__方法 创建实例时把一些属性绑上去 __init__方法第一参数永远是self 表示船舰的实例本身 类是实例的模板 实例是一个一个具体的对象 ...
- 向mysql中导入向导时如表xlsx
如果出现这种问题那么是因为没有打开这个文件,如果想导入这个文件需要到开这个文件,然后再导入
- sublime2 nodejs 执行编译无反应
这个问题困扰了我得一周了,好不容易解决了, 一.问题描述: 安装网上的一些教程在sublime text 2 里面安装Nodejs 的编译环境,但是安装完之后执行编译没有任何输出信息,编译没有反应,只 ...
- scrapy递归解析和post请求
递归解析 递归爬取解析多页页面数据 每一个页面对应一个url,则scrapy工程需要对每一个页码对应的url依次发起请求,然后通过对应的解析方法进行作者和段子内容的解析. 实现方案: 1.将每一个页码 ...
- 简繁体转化处理 opencc 安装【centos 7】
代码 #准备工作 yum install cmake yum install git #下载代码 git clone https://github.com/BYVoid/OpenCC #安装文档生成 ...