java.sql.Time 字段时区问题 Mybatis 源码分析

java.sql.Time 字段时区问题 系列文章目录

第一章 初步分析
第二章 Mybatis 源码分析
第三章 Jackson 源码分析 意想不到的Time处理类


文章目录

  • java.sql.Time 字段时区问题 系列文章目录
  • 前言
  • Mybatis源码阅读
    • 1. ResultSetImpl部分源码:
    • 2. SqlTimeValueFactory部分源码:
      • 2.1 SqlTimeValueFactory debug 截图
  • 分析
  • 总结


前言

初步分析文中,主要针对项目部署服务器时区、数据库时区、Jvm运行设置时区和java.sql.Time字段序列化过程时区问题进行展开分析。并给出三个可能问题相对应的解决方案。但是,前段时间又出现时区问题。让我必须重新思考此问题。

以下内容主要对Mybatis源码进行阅读,理解分析java.sql.Time字段持久化过程,并定位时区问题。


Mybatis源码阅读

Mybatis对于结果集的处理流程:DefaultResultSetHandler -> handleResultSets() -> handleResultSet() -> handleRowValues() -> handleRowValuesForSimpleResultMap() -> getRowValue() -> applyAutomaticMapping() -> TypeHandler -> getResult() -> SqlTimeTypeHandler -> getNullableResult() -> ResultSet -> getTime().

SqlTimeTypeHandler对应处理java.sql.Time, 通过TypeHandlerRegistry注册的默认类型处理器。

因为项目中使用的DruidDataSource,所以ResultSet的包装类为DruidPooledResultSet,在处理getTime时,Mybatis的SqlTimeTypeHandler直接调用的getTime(columeName)签名方法,该签名方法实际实现类是ResultSetImpl(mysql-connector-java), 该实现类中getTime方法重载有多个,但是最终都需要用到一个Calendar对象做时间转换,将mysql的时间类型转换为java.sql.Time

1. ResultSetImpl部分源码:

/** This program is also distributed with certain software (including but not* limited to OpenSSL) that is licensed under separate terms, as designated in a* particular file or component or in included license documentation. The* authors of MySQL hereby grant you an additional permission to link the* program and your derivative works with the separately licensed software that* they have included with MySQL.** Without limiting anything contained in the foregoing, this file, which is* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,* version 1.0, a copy of which can be found at* http://oss.oracle.com/licenses/universal-foss-exception.*/package com.mysql.cj.jdbc.result;import java.sql.Time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;public class ResultSetImpl extends NativeResultset implements ResultSetInternalMethods, WarningListener {/** Time字段内容生成仓库 实际初始化 SqlTimeValueFactory */private ValueFactory<Time> defaultTimeValueFactory;@Overridepublic Time getTime(int columnIndex) throws SQLException {checkRowPos();checkColumnBounds(columnIndex);//这个defaultTimeValueFactory初始化也是SqlTimeValueFactoryreturn this.thisRow.getValue(columnIndex - 1, this.defaultTimeValueFactory);}@Overridepublic Time getTime(int columnIndex, Calendar cal) throws SQLException {checkRowPos();checkColumnBounds(columnIndex);//最终调用, 注意这个valueFactory,最终是这个处理时间的ValueFactory<Time> vf = new SqlTimeValueFactory(this.session.getPropertySet(), cal,cal != null ? cal.getTimeZone() : this.session.getServerSession().getServerTimeZone());return this.thisRow.getValue(columnIndex - 1, vf);}
}

从下面代码可以看出来,处理java.sql.Time字段主要是通过 SqlTimeValueFactory进行处理

    @Overridepublic Time getTime(int columnIndex, Calendar cal) throws SQLException {checkRowPos();checkColumnBounds(columnIndex);ValueFactory<Time> vf = new SqlTimeValueFactory(this.session.getPropertySet(), cal,cal != null ? cal.getTimeZone() : this.session.getServerSession().getServerTimeZone());return this.thisRow.getValue(columnIndex - 1, vf);}

2. SqlTimeValueFactory部分源码:

/*** This program is also distributed with certain software (including but not* limited to OpenSSL) that is licensed under separate terms, as designated in a* particular file or component or in included license documentation. The* authors of MySQL hereby grant you an additional permission to link the* program and your derivative works with the separately licensed software that* they have included with MySQL.** Without limiting anything contained in the foregoing, this file, which is* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,* version 1.0, a copy of which can be found at* http://oss.oracle.com/licenses/universal-foss-exception.*/package com.mysql.cj.result;import java.sql.Time;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;import com.mysql.cj.WarningListener;
import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.protocol.InternalTime;/*** A value factory to create {@link java.sql.Time} instances. As with other date/time types, a time zone is necessary to interpret the* time values returned from the server.*/
public class SqlTimeValueFactory extends AbstractDateTimeValueFactory<Time> {private WarningListener warningListener;// cached per instance to avoid re-creation on every create*() callprivate Calendar cal;public SqlTimeValueFactory(PropertySet pset, Calendar calendar, TimeZone tz) {super(pset);if (calendar != null) {this.cal = (Calendar) calendar.clone();} else {// c.f. Bug#11540 for details on locale// 此处 TimeZone tz 时区是通过 连接Session获取时区,即连接Mysql数据库的时区设置this.cal = Calendar.getInstance(tz, Locale.US);this.cal.setLenient(false);}}public SqlTimeValueFactory(PropertySet pset, Calendar calendar, TimeZone tz, WarningListener warningListener) {this(pset, calendar, tz);// warningLinster 就是 ResultSetImplthis.warningListener = warningListener;}@Overridepublic Time localCreateFromTime(InternalTime it) {if (it.getHours() < 0 || it.getHours() >= 24) {throw new DataReadException(Messages.getString("ResultSet.InvalidTimeValue", new Object[] { "" + it.getHours() + ":" + it.getMinutes() + ":" + it.getSeconds() }));}synchronized (this.cal) {try {// c.f. java.sql.Time "The date components should be set to the "zero epoch" value of January 1, 1970 and should not be accessed."this.cal.set(1970, 0, 1, it.getHours(), it.getMinutes(), it.getSeconds());this.cal.set(Calendar.MILLISECOND, 0);long ms = (it.getNanos() / 1000000) + this.cal.getTimeInMillis();return new Time(ms);} catch (IllegalArgumentException e) {throw ExceptionFactory.createException(WrongArgumentException.class, e.getMessage(), e);}}}
}

InternalTime 源码:本身就是一个内部时间,和时区无关

/** This program is also distributed with certain software (including but not* limited to OpenSSL) that is licensed under separate terms, as designated in a* particular file or component or in included license documentation. The* authors of MySQL hereby grant you an additional permission to link the* program and your derivative works with the separately licensed software that* they have included with MySQL.** Without limiting anything contained in the foregoing, this file, which is* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,* version 1.0, a copy of which can be found at* http://oss.oracle.com/licenses/universal-foss-exception.*/package com.mysql.cj.protocol;public class InternalTime {private int hours = 0;private int minutes = 0;private int seconds = 0;private int nanos = 0;private int scale = 0;/*** Constructs a zero time*/public InternalTime() {}public InternalTime(int hours, int minutes, int seconds, int nanos, int scale) {this.hours = hours;this.minutes = minutes;this.seconds = seconds;this.nanos = nanos;this.scale = scale;}
}

通过对源码查看可以知道,java.sql.Time字段的持久化过程中,真正调用了localCreateFromTime()方法,获取持久化生成的InternalTime对象,设置Calendar对象的时间参数来生成java.sql.Time对象,实际生成过程中使用的以1970年1月1日00:00:00 GMT标准基准时间的毫秒数,并没有由于时区问题做时间的计算更改。

实际核心代码如下:

	this.cal.set(1970, 0, 1, it.getHours(), it.getMinutes(), it.getSeconds());this.cal.set(Calendar.MILLISECOND, 0);long ms = (it.getNanos() / 1000000) + this.cal.getTimeInMillis();

2.1 SqlTimeValueFactory debug 截图

warningListener

分析

通过对以上源码的分析,知道Mybatis如何把数据库中Time类型字段持久化成java.sql.Time对象,并且持久化过程中并未发现有关时区问题。

但是由于java.sql.Time类是继承java.util.Date类并屏蔽年月日的类,还是和时区有关,以下测试可以看出来:
同一个Time对象在不同时区下展示日内时间是随时区变化而变化的。
Time测试


总结

通过以上分析可以得出,Mybatis有对java.sql.Time字段专门处理类,过程正确无误,并不存在时区问题。

但是生成Time对象在不同时区下展示日内时间是随时区变化而变化的。因此,如果项目中出现时区问题,还是可能是以下情况:

  1. 服务器时区,突然被改变
  2. jvm时区,突然被改变
  3. jackson 对 java.sql.Time 的序列化并未考虑时区,即使指定时区也不起效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/1441.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AUTOSAR】BMS开发实际项目讲解(二十九)----电池管理系统电池充放电功率控制与SOC

电池充放电功率控制 关联的系统需求 Sys_Req_3901、Sys_Req_3902、Sys_Req_3903、Sys_Req_3904; 功能实现描述 电池充放电功率控制主要包括以下内容&#xff1a; 60S可用功率 参见[CELL] 30S可用功率 参见[CELL] 10S可用功率 参见[CELL] SOP算法 ID Description ASI…

Nginx(3)nginx的Rewrite功能

nginx跨域 Rewrite功能配置Rewrite的相关命令Rewrite的案例域名跳转域名镜像独立域名目录自动添加/合并目录防盗链 Rewrite功能配置 Rewrite是Nginx服务器提供的一个重要基本功能&#xff0c;是Web服务器产品中几乎必备的功能。主要的作用是用来实现URL的重写。 注意:Nginx服…

【iVX】在百花齐放的低代码平台中独领风骚

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

openssl版本升级与降级

openssl版本升级与降级 flyfish 环境 Ubuntu 22.04 1.1.1升级3.1.1 查看openssl版本 openssl versionOpenSSL 1.1.1t 7 Feb 2023https://www.openssl.org/source/ 编译和安装 ./config --prefix/usr/local/openssl311 make -j8 make install进入/usr/local/openssl311/l…

Web服务器群集:Nginx+Tomcat实现负载均衡与动静分离集群

目录 一、理论 1.多实例 2.Nginx负载均衡 3.Nginx动静分离 4.配置NginxTomcat负载均衡 5.配置NginxTomcat动静分离集群 6.Nginx 四层代理配置 二、实验 1.配置NginxTomcat负载均衡 2.、配置NginxTomcat动静分离集群 三、问题 1.服务器群集与集群的区别 四、总结 一…

【数据结构】栈和队列(栈篇)

目录 1.栈的概念及结构 2.栈的实现 2.1栈的结构体定义 2.2栈的常用接口函数 &#x1f43e;栈的初始化 &#x1f43e;插入数据 &#x1f43e;删除数据 &#x1f43e;取栈顶元素 &#x1f43e;判断栈是否为空 &#x1f43e;计算栈的大小 &#x1f43e;栈的销毁 2.3完…

Java设计模式之结构型-适配器模式(UML类图+案例分析)

目录 一、概念 二、UML类图 1、类适配器 2、对象适配器 三、角色设计 四、代码实现 案例一 案例二 五、总结 一、概念 将一个类的接口转换为另一个接口&#xff0c;使得原本由于接口不兼容的类进行兼容。 适配器模式主要分为类适配器模式和对象适配器模式&#xff0…

android:RadioGroup的使用

一、前言&#xff1a;工作中会遇到勾选不同的类型&#xff0c;获得不同的数据。仅以此笔记记录。 二、上代码&#xff1a; 新建一个Activity public class RadioHorizontalActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {private Text…

conda、python与人工智能学习过程中的一些基础性问题

一个不知名大学生&#xff0c;江湖人称菜狗original author: Jacky LiEmail : 3435673055qq.com Time of completion&#xff1a;2023.6.30 Last edited: 2023.6.30 目录 pip install XXX与conda install XXX的区别 conda install xxx pip install xxx 为什么要建立虚拟环境…

Mysql架构篇--Mysql(M-M) 主从同步

文章目录 前言一、M-M 介绍&#xff1a;二、M-M 搭建&#xff1a;1.Master1&#xff1a;1.1 my.cnf 参数配置&#xff1a;1.2 创建主从同步用户&#xff1a;1.3 开启复制&#xff1a; 2.Master2&#xff1a;2.1 my.cnf 参数配置&#xff1a;2.2 创建主从同步用户&#xff1a;2.…

解决联网时自动打开浏览器转到必应msn网址的问题

现象 开机后或者断网重连之后&#xff0c;系统自动打开默认浏览器&#xff08;不管是IE还是谷歌&#xff0c;或其他的浏览器&#xff09;网址为http://go.microsoft.com/fwlink/?LinkID219472&clcid0x409接着转到http://cn.bing.com/ 或者 https://www.msn.com/ 解决方法…

ATA-3090功率放大器在新能源汽车上的应用

随着全球对环境保护和节能减排的重视&#xff0c;新能源汽车正逐渐成为汽车市场的主流。而功率放大器作为电子控制系统中的关键部件之一&#xff0c;也扮演着越来越重要的角色。那么&#xff0c;功率放大器在新能源汽车上的应用有哪些呢&#xff1f; 图&#xff1a;新能源汽车 …