JDBC-Mysql 时区问题详解
目录
一、前置准备
1.1 版本号列表
1.2 Sql脚本
1.3 application.yaml配置
1.4 数据库时区设置
二、java Date类型与(jdbcType)TIMESTAMP类型的转换
2.1 jdbc对serverTimeZone的处理
2.2 java Date转(jdbcType)TIMESTAMP
2.3 (jdbcType)TIMESTAMP转java Date
三、结论
四、扩展
4.1 将数据库时区设置为+8
4.2 数据库时间展示
4.3 java代码获取到的时间展示
之前对Mysql的两种时间类型有过简单的了解,知道「mysql中有两个时间类型,timestamp与datetime,其中timestamp在存储上是包含时区的,而datetime是不包含时区的字符串形式。而通常应用下所说的时区问题,也指的是Java应用使用了jdbc驱动时,存储和读取的时区不一致的问题」。
但是mybatis是如何将java的java.util.Date类型转成mysql的datetime, timestamp类型存储到db,同时是如何将db中datetime, timestamp类型的数据转成java的Date类型的,本人并没有进行深入的研究,导致在出现时区问题时不能找到根源,故针对源码深入研究了一下,做此记录,供大家借鉴。
在探讨时区问题之前,默认大家对GMT时间,UTC时间,时区等概念已经了解。需要注意的是,java的时间在存入db之前,是通过jdbc驱动转成jdbcTyep的TIMESTAMP类型的,同理查询时,也是由jdbcTyep的TIMESTAMP类型转成java时间类型。这个稍后我们会讲到,同样可以在mybatis的mapper.xml文件中得到印证:
<result property="gmtCreate" column="gmt_create" jdbcType="TIMESTAMP"/>
<result property="gmtModified" column="gmt_modified" jdbcType="TIMESTAMP"/>
这里指定了jdbcType为TIMESTAMP,也就是说,不管mysql中是datetime还是timestamp类型,对应到jdbcTypef都是TIMESTAMP类型,所以研究java时间和 (jdbcType)TIMESTAMP的转换,是我们理解时区问题的关键。
一、前置准备
1.1 版本号列表
本文研究所得结论均基于此处列出的软件版本号,其他版本号结论或有出入,但原理可供参考。
springboot版本号 | 3.3.5 |
mybatis-plus版本号 | 3.5.7 |
mysql版本号 | 9.0.1 |
mysql驱动版本号 | com.mysql.cj.jdbc.Driver: 8.2.0 |
jdk版本号 | openjdk version "17.0.13" |
1.2 Sql脚本
DROP TABLE IF EXISTS `pds_user`;
CREATE TABLE IF NOT EXISTS `pds_user`
(`id` bigint PRIMARY KEY auto_increment,`name` varchar(10) NOT NULL COMMENT '姓名',`email` varchar(30) NOT NULL COMMENT '邮箱',`gmt_create` datetime NOT NULL DEFAULT (now()),`gmt_modified` timestamp NOT NULL DEFAULT (now())
);
1.3 application.yaml配置
serverTimezone=GMT%2B9,即设置连接时区为+9时区。
server:port: 8080spring:application:name: TestProgramdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/pds?serverTimezone=GMT%2B9username: rootpassword: 123456mybatis-plus:mapper-locations: classpath*:mapper/*.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
1.4 数据库时区设置
SET GLOBAL time_zone = '+7:00';SELECT @@global.time_zone;
二、java Date类型与(jdbcType)TIMESTAMP类型的转换
先说结论:Java的Date类型实际上是经过jdbc转换为字符串的形式写入数据库,查询时同理以字符串的形式转换为Java Date类型。时区转换时只和jdbcUrl指定的serverTimeZone有关,和数据库配置的时区无关。
下面我们进行验证:
2.1 jdbc对serverTimeZone的处理
这个处理主要在com.mysql.cj.protocol.a.NativeProtocol#configureTimeZone的方法中进行的。jdbc驱动会从jdbcUrl中取时区设置到serverSession中,如果没有则取默认时区。我们断点来看。
- 首先去拿serverTimezone指定的时区,这里获取到的是GMT+9时区;
- 将serverSession的timeZone设置为+9时区;
- 需要注意的是,如果没有从连接中获取到时区,就会用TimeZone.getDefault()获取到应用运行的本地默认时区(我们这里是GMT+8)。
2.2 java Date转(jdbcType)TIMESTAMP
jdbc将java的Date根据指定的时区转成时间字符串形式,写入数据库,这一过程和数据库时区配置无关。
测试代码:
因为是jdbc规范定义的接口,所以我们直接找到java.sql.PreparedStatement接口(稍早一些的同学应该都手写过jdbc的crud吧。。。),发现定义了一个setTimestamp()方法;我们断点到这里,发现会走到com.mysql.cj.jdbc.ClientPreparedStatement实现类的,ClientPreparedStatement#setTimestamp的1728行,会调到com.mysql.cj.NativeQueryBindings#setTimestamp方法,该方法第469行又调到com.mysql.cj.NativeQueryBindValue#setBinding。
setBinding方法中第153行会初始化valueEncoder。我们发现这个valueEncoder是SqlTimestampValueEncoder类型,
转到SqlTimestampValueEncoder发现会调用com.mysql.cj.protocol.a.SqlTimestampValueEncoder#getString方法,该方法会指定时间格式化字符串和时区(+9),将date转成string返回。
可以看到,转化成的字符串是17点09,而此时,我本地的时间是+8时区的16点09。(截图延迟...),所以最终setTimestamp()方法会将java的date转成指定时区(此时是+9)的字符串返回。
断点继续往下走,会发现最终走到com.mysql.cj.NativeQueryBindValue#writeAsText方法,
该方法的参数intoMessage底层是个byteBuffer,而byte根据ascii值转换后会发现,其实是拼好的sql语句。
这里方法还没有执行完,所以sql语句没有拼完,如果执行完全后,就行拼成最终的sql语句。然后由mysql来执行语句。
注意:控制台的打印还是服务器本地时间(GMT+8)时间,并不是拼好的sql语句中的GMT+9时间,这个算是个不好的体验吧。。。
最终落库(时间为拼好的sql中的时间):
2.3 (jdbcType)TIMESTAMP转java Date
jdbc将获取到的数据库时间字符串,根据时区转换成java的Date类型,这一过程和数据库的时区配置无关。
测试代码:
同理,有setTimestamp方法就有getTimestamp方法,研究jdbc规范的java.sql.ResultSet接口,发现有getTimestamp方法,断点到这里,发现会进到com.mysql.cj.jdbc.result.ResultSetImpl#getTimestamp(int)方法中,
这个方法的943行会调到com.mysql.cj.protocol.a.result.ByteArrayRow#getValue方法,
继续断点下去,会进到com.mysql.cj.protocol.a.MysqlTextValueDecoder#decodeTimestamp方法。从这个类名和方法名我们大概能看出来,这个会将mysql的textValue值解码成timestamp类型。继续看这个方法的89行,会先调getTimestamp方法,创建出一个com.mysql.cj.protocol.InternalTimestamp类(时间为17点09)。
然后再调用createFromTimestamp方法,最终调到com.mysql.cj.result.SqlTimestampValueFactory#localCreateFromTimestamp方法,将InternalTimestamp时间根据+9时区转成当前时间的时间戳,再组装Timestamp对象返回(+8时间)。
点进去看这个Timestamp,其实是继承自java的Date,new Date(long mills)同样接收一个时间戳作为参数。
结果验证,输出的是(+8时间):
三、结论
- Java的Date类型实际上是经过jdbc转换为字符串的形式写入数据库,查询时同理以字符串的形式转换为Java Date类型。
- java代码时间转换的时区,只和jdbcUrl指定的serverTimeZone(本例是+9)有关,和mysql服务器的时区(本例是+7)无关。
四、扩展
如果将数据库的时区再改成+8,那么数据库显示和java代码获取到的时间会发生怎么的变化?
4.1 将数据库时区设置为+8
SET GLOBAL time_zone = '+8:00';SELECT @@global.time_zone;
4.2 数据库时间展示
这个我们应该都知道,datetime类型的时间没有变化,timestamp类型的时间从之前+7时区的17点09变成+8时区的18点09
4.3 java代码获取到的时间展示
先猜一手结论,dateime类型的时间数据,和之前获取到的一样,展示16点09,timestamp类型的时间数据,先转成InternalTimestamp类(+9时区的18点09),然后转成时间毫秒值(距离1970-01-01)最后转成java.sql.Timestamp,也就是展示+8时间的17点09。
结果展示:
和我们预期的一模一样!这个案例再次印证了,jdbc通过字符串读写,且与数据库时区配置无关。
其他:本文其他代码全部是mybatisX-Generator生成的,过于简单,就不贴出源码了。
相关文章:

JDBC-Mysql 时区问题详解
目录 一、前置准备 1.1 版本号列表 1.2 Sql脚本 1.3 application.yaml配置 1.4 数据库时区设置 二、java Date类型与(jdbcType)TIMESTAMP类型的转换 2.1 jdbc对serverTimeZone的处理 2.2 java Date转(jdbcType)TIMESTAMP …...

前端页面一些小点
案例一: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>快递单号查询</title><…...

Postman接口测试(断言、关联、参数化、输出测试报告)
基本界面展示 Get、Post请求 Postman断言 使用postman来判断预期结果与实际结果是否一致 响应状态码断言 响应包含字符串 断言判断字符串的格式 关联 用于解决http请求之间存在依赖关系 依赖:一个http请求的响应结果中的数据,被另一个请求使用 登…...
redis和mongodb等对比分析
Redis 和 MongoDB 都是非常流行的 NoSQL 数据库,它们在数据存储模型、性能、扩展性等方面有很大的差异。下面是 Redis 和 MongoDB 的对比分析: 1. 数据模型 Redis: 键值存储:Redis 是一个内存数据结构存储,它支持多种数据类型,如字符串、哈希、列表、集合、有序集合等。…...

如何在 WordPress 中轻松强制所有用户退出登录
作为一名长期管理 WordPress 网站的站长,我深知维护网站安全性的重要性。尤其是在面对会员网站或付费内容平台时,确保所有用户的登录状态是最新的,是维持网站正常运营的关键之一。今天,我就分享一下如何通过简单的步骤,…...

移除元素(leetcode 27)
给定一个数组,在数组中删除等于这个目标值的元素,然后返回新数组的大小 数组理论: 数组是一个连续的类型相近的元素的一个集合,数组上的删除是覆盖,只能由后面的元素进行覆盖,而不能进行真正意义上的地理位…...
html5表单属性的用法
文章目录 HTML5表单详解与代码案例一、表单的基本结构二、表单元素及其属性三、表单的高级应用与验证四、表单布局与样式 HTML5表单详解与代码案例 HTML5表单是网页中用于收集用户输入并提交到服务器的重要元素,广泛应用于登录页面、客户留言、搜索产品等场景。本文…...

使用 Ant Design Vue 自定渲染函数customRender实现单元格合并功能rowSpan
使用 Ant Design Vue 自定渲染函数customRender实现单元格合并功能rowSpan 背景 在使用Ant Design Vue 开发数据表格时,我们常常会遇到需要合并单元格的需求。 比如,某些字段的值可能会在多行中重复出现,而我们希望将这些重复的单元格合并为…...
相机光学(四十四)——ALL-PD和PDAF
1.PDAF(Phase Detection Auto Focus) PDAF是相位检测自动对焦技术的缩写,它是一种在数码相机和智能手机摄像头中使用的自动对焦技术。 PDAF的原理是根据CIS(CMOS图像传感器)不同像素的相位差信息,判断出…...

Opengl光照测试
代码 #include "Model.h" #include "shader_m.h" #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" //以上是放在同目录的头文件#include <glad/glad.h> #include <GLFW/glfw3.…...
OpenSIP2.4.11 向 FreeSWITCH 注册
应朋友要求做了个简单的测试,花费时间不过半小时,记录如下: OpenSIPS IP 地址:192.168.31.213 FreeSWITCH IP 地址:192.168.31.166 加载 uac_registrant 模块(这个模块依赖 uac_auth 模块,得…...

【C++】深入理解 C++ 优先级队列、容器适配器与 deque:实现与应用解析
个人主页: 起名字真南的CSDN博客 个人专栏: 【数据结构初阶】 📘 基础数据结构【C语言】 💻 C语言编程技巧【C】 🚀 进阶C【OJ题解】 📝 题解精讲 目录 前言📌 1. 优先级队列、容器适配器和 deque 概述✨1.1 什么是优…...
Android 开发与救砖工具介绍
Android 开发与救砖工具介绍 在 Android 开发和设备维护中,fastboot、adb 和 9008 模式是三个非常重要的工具和模式。它们各自有不同的用途和操作方式,对于开发者和技术支持人员来说,了解它们的功能和使用方法是必不可少的。 1. Fastboot …...
vue2和vue3:diff算法的区别?
Vue 2 和 Vue 3 在 diff 算法方面的主要区别是: Vue 2 使用普通的 diff 算法,它会遍历所有的节点进行比对。 Vue 3 引入了 patch flag 的概念,并且对 diff 算法进行了优化,比如在相同层级的节点间不会去递归比对已经被移除的节点…...
后端返回大数问题
这个问题并不难,但是在开发的时候没有注意到 后端返回了一个列表数据,包含id,这个id是一个大数,列表进入详情,需要将id传入到详情页面详情页面内部通过id获取数据一直404,id不正确找问题,从路由传参到请求数据发现id没有问题,然后和后端进行联调,发现后端返回的id和我获取的id…...

vue3: ref, reactive, readonly, shallowReactive
vue3: ref, reactive, readonly, shallowReactive 原文地址:https://mp.weixin.qq.com/s/S3jPZKEMBP8nQQObF5d2VA <template><!-- <ul><li v-for"item in list.arr">{{item}}</li></ul><button click.prevent"add"…...

5G与4G互通的桥梁:N26接口
5G的商用部署进程将是一个基于4G系统进行的长期的替换、升级、迭代的过程,4G系统是在过渡到5G全覆盖过程中,作为保障用户业务连续性体验这一目的的最好补充。 因此4G/5G融合组网,以及互操作技术将是各大运营商在网络演进中需要重点考虑的问题…...
29-Elasticsearch 集群监控
Elasticsearch Stats 相关的 API ● Elasticsearch 提供了多个监控相关的 API ○ Node Stats: _nodes/stats ○ Cluster Stats: _cluster/stats ○ Index Stats: index_name/_stats Elasticsearch Task API ● 查看 Task 相关的 API ○ Pending Cluster Tasks…...

利用Excel批量生成含二维码的设备管理标签卡片
在日常办公中,批量制作标签是常见且繁琐的任务,尤其当这些标签需要包含大量数据并附带二维码以便快速扫描识别时,难度更是成倍增加。尽管传统的Word邮件合并功能在数据插入方面表现出色,但在二维码生成上却显得有些捉襟见肘。 为…...

小米运动健康与华为运动健康在苹手机ios系统中无法识别蓝牙状态 (如何在ios系统中开启 蓝牙 相册 定位 通知 相机等功能权限,保你有用)
小米运动健康与华为运动健康在苹手机ios系统中无法识别蓝牙状态 (解决方案在最下面,参考蓝牙权限设置方式举一反三开启其它模块的权限) 最近买了一台小米手表s4,但是苹手机ios系统中的 “小米运动健康” app 始终无法识别我手机…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...