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

news2024/11/16 6:50:07

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;

    @Override
    public Time getTime(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        //这个defaultTimeValueFactory初始化也是SqlTimeValueFactory
        return this.thisRow.getValue(columnIndex - 1, this.defaultTimeValueFactory);
    }

    @Override
    public 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进行处理

    @Override
    public 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*() call
    private 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 就是 ResultSetImpl
        this.warningListener = warningListener;
    }

    @Override
    public 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.coloradmin.cn/o/690093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git分支的更新按钮不可用,idea 的git命令无法识别

现象 从git分支签出其本地分支&#xff0c;但是签出之后该分支的更新按钮为灰色&#xff0c;我只能拉取&#xff0c;不能更新&#xff0c;也不能推送 解决 打开idea的终端&#xff0c;执行命令git branch --set-upstream-toorigin/远程上的分支名 。又引出另外一个问题无法识别…

Java阶段四Day09

Java阶段四Day09 文章目录 Java阶段四Day09过滤器创建过滤器过滤器的作用执行过滤器 为什么写配置文件关于登录和认证的区别前端获取用户状态 过滤器 使用JWT取代Session&#xff0c;不管做什么都携带着JWT&#xff0c;那么服务端不管处理什么请求、或绝大多数请求都要尝试获取…

如何让金鸣识别转出来的excel表名不为域名?

我们知道&#xff0c;金鸣表格文字识别系统&#xff08;简称金鸣识别&#xff09;可以将图片转为excel表格&#xff0c;使用起来非常方便&#xff0c;由于它采用了超前AI的技术&#xff0c;且经过深度学习&#xff0c;所以它的识别准确率也非常高&#xff0c;让我们节省了不少人…

基于html+css的图展示137

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

jenkins 自动安装nodejs16.16.0版本报错处理

jenkins 自动安装nodejs16.16.0版本报错了&#xff0c;好像访问不了下面的网址&#xff0c;所以下载不了文件报错 那就人工安装吧&#xff0c;下载16.16.0nodejs版本后&#xff0c;到 在这个目录下安装/root/.jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation …

火车售票管理系统

1、项目概要 2 2、数据库设计 2 2.1 数据表 2 2.2 业务表及其字段 4 3、设计与实现 16 3.1业务功能1名称&#xff1a;系统管理 16 3.1-1 员工管理 16 3.1-2 角色维护 18 3.1-2 操作员管理 19 3.2业务功能2名称&#xff1a;列车管理 21 3.2-1 车辆管理 21 3.2-2 站点管理 23 3.2…

021:vue中watch监听页面变化 动态设置iframe元素的高度

第021个 查看专栏目录: VUE — element UI vue项目中如何动态的设置iframe的高度的呢&#xff0c;这里主要用到了监听的功能&#xff0c;页面高度变化时&#xff0c;设定的动态高度也会相应的改变。 template中&#xff1a; <iframe width"100%" :height"f…

maven使用profile动态配置(IDEA演示)

简介 使用Maven的profile来切换不同环境(开发/测试/发布生产)的配置文件 需求简介 通过把不同环境的配置参数信息,放到不同环境的配置文件里(例如:dev.properties开发环境配置文件).Maven在打包的时候根据选择不同的环境(例如dev:生产环境)调用对应的配置文件(dev.properties)…

FPGA XDMA 中断模式实现 PCIE X8 上位机视频传输 提供工程源码和QT上位机源码

目录 1、前言2、我已有的PCIE方案3、PCIE理论4、总体设计思路和方案图像产生、发送、缓存XDMA简介XDMA中断模式图像读取、输出、显示QT上位机及其源码 5、vivado工程详解6、上板调试验证7、福利&#xff1a;工程代码的获取 1、前言 PCIE&#xff08;PCI Express&#xff09;采…

springboot 国家公务员招聘网站-计算机毕设 附源码80528

springboot 国家公务员招聘网站 目 录 摘要 1 绪论 1.1研究背景与意义 1.2开发现状 1.3系统开发技术的特色 1.4springboot框架介绍 1.5论文结构与章节安排 2 2国家公务员招聘网站系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.3.2数据修改流程 2.…

【(不用Ajax)解决 Layui 插件分页点下一页后又自动跳回前一页的问题】

问题描述: 我们在使用Layui插件进行分页会出现一个常见的问题。就是假如我们不用异步的方法进行分页获取后端的数据时&#xff0c;进行window.location.href进行跳转的进行后端分页。 会遇到: 切换页面的时候&#xff0c;点下一页后又自动跳回前一页的问题 原因:有一个curr属…

论文翻译:2021_Real-Time Denoising and Dereverberation wtih Tiny Recurrent U-Net

论文地址&#xff1a;微型循环U-Net实时降噪和去混响 论文代码&#xff1a; https://github.com/YangangCao/TRUNethttps://github.com/amirpashamobinitehrani/tinyrecurrentunet 引用格式&#xff1a;Choi H S, Park S, Lee J H, et al. Real-Time Denoising and Dereverbera…

将画面上的DOM元素转为SVG并且导出

需求如下&#xff1a; import { elementToSVG } from dom-to-svg; let dom document.querySelector(#pane-${this.currentTabIndex}); const svgDocument elementToSVG(dom); const svgString new XMLSerializer().serializeToString(svgDocument); common.download(this.…

Oracle把单行数据拆分成多行

背景&#xff1a;把接收人[JSRID]拆分为多行&#xff0c;接收人是存在一个字段中&#xff0c;以逗号隔开&#xff0c;每次会议的接收人数量不一 建表语句&#xff1a; CREATE TABLE "HYTZGGZS_HYTZGGXXTJ" ("WF_ORUNID" VARCHAR2(255 BYTE) VISIBLE,&quo…

图像增强之图像锐化(边缘增强)之prewitt算子

note matx (-1,0,1;0,0,0;-1,0,1) maty (-1,-1,-1;0,0,0;1,1,1) code // 图像增强之图像锐化(边缘增强)之prewitt算子 void GetPrewittMat(Mat& prewittMatX, Mat& prewittMatY) {prewittMatX (Mat_<int>(3,3) << -1,0,1,0,0,0,-1,0,1);prewittMatY…

linux系统Nginx服务页面优化

文章目录 一、Nginx服务优化1.隐藏版本号2.日志分割3.设置页面压缩4.配置网页缓存时间5.设置连接超时6.Nginx并发设置优化&#xff08;更改进程数&#xff09;7.修改用户与组8.配置防盗链 总结 一、Nginx服务优化 1.隐藏版本号 隐藏Nginx版本号&#xff0c;以避免泄露Nginx的…

使用QFIL刷机步骤

目录 0.注意 1.确认是否出现端口 2. 使用abd命令开启下载端口 3.选择生成类型 4.配置为UFS 5.选择文件 6.加载文件 7.点击Download下载 0.注意 连接手机与电脑时需要手机是开机状态&#xff0c;否则连接不上 操作过程中不能对手机进行任何操作 若操作中未选择&#xff1a…

Python巧解数字变换问题,有点意思(65)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 和猫妹学Python&#xff0c;一起趣味学编程。 今日主题 如何用Python解决数字变换问题 比如&#xff1a; 对于一对正整数a和b&#xff0c;对a只能进行加1&#xff0c;减1…

chatgpt赋能python:Python计算器编程:从入门到精通

Python计算器编程&#xff1a;从入门到精通 随着计算机技术的不断发展&#xff0c;计算器已经成为了我们日常生活中必不可少的工具。Python是一种简单易学的编程语言&#xff0c;也是一种十分适合编写计算器程序的语言。 为什么选择Python编写计算器程序&#xff1f; Python…

网络安全合规-ISO 27701

ISO 27701是什么&#xff1f; 是ISO 27001和 ISO 27002的扩展内容&#xff0c;对建立、实施、维护和持续改进隐私信息管理系统&#xff08;PIMS&#xff09;的各项要求做出了规定&#xff0c;是首部针对隐私信息管理的国际标准。该标准概述了适用于个人可识别信息控制者和处理者…