性能文章>【全网首发】TimeZone-datetime在JVM时区和MySQL Session时区的转换>

【全网首发】TimeZone-datetime在JVM时区和MySQL Session时区的转换原创

435137

引言

JVM时区配置-两行代码让我们一帮子人熬了一个通宵】描述了由于代码BUG导致存储到数据库的时间比正常时间少八小时的案例。案例中对于数据库字段类型是datetime和timestamp的时区转换关系进行了描述,本文试图从代码角度描述以下逻辑:

  • JDBC场景下MySQL Session时区如何配置的
  • JDBC场景下datetime类型的数据如何转换的

测试环境

MySQL

配置项 说明
MySQL version Windows MySQL Server 8.0.30.0
time_zone +08:00
system_time_zone
创建测试库 create database test;
创建测试表 create table datetimetest( dt datetime);

应用信息

java version

java version "1.8.0_341"
Java(TM) SE Runtime Environment (build 1.8.0_341-b10)
Java HotSpot(TM) Client VM (build 25.341-b10, mixed mode, sharing)

pom

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.30</version>
</dependency>

分析过程

测试场景

  • JVM是UTC + 8,MySQL time_zone是UTC + 8,MySQL JDBC Driver配置的是UTC + 0
  • JVM 应用程序原始时间是(UTC + 8):2022-10-16 10:00:00
  • MySQL JDBC Driver发送给MySQL server的时间是:2022-10-16 02:00:00(时间由UTC + 8转换为UTC + 0)
  • MySQL server最终存储的时间为:2022-10-16 02:00:00
  • MySQL JDBC Driver从数据库中查出的时间是:2022-10-16 02:00:00
  • 应用程序最终读取到的时间是:2022-10-16 10:00:00

测试代码

image.png

getConnection

image.png
从图中看创建一个数据库连接是个非常重量级的操作,选择一个高效的连接池很重要。与本篇文章主要相关的是图中斜体红色加粗部分。

关注点一

关注跟time_zone相关的几个配置项。
相关类及配置说明文件:PropertyDefinitions、LocalizedErrorMessages.properties。

配置项 默认值 sinceVersion
connectionTimeZone 字符串类型,默认值:null 3.0.2
forceConnectionTimeZoneToSession 布尔类型,默认值:false 8.0.23
preserveInstants 布尔类型,默认值:true 8.0.23

关注点二

image.png

executeUpdate

PreparedStatement的实现类是:com.mysql.cj.jdbc.ClientPreparedStatement,跟本次文章相关的内容如下:

编码器

在NativeProtocol类初始化的时候会将不同数据类型的编码器注册&初始化:

static Map<Class<?>, Supplier<ValueEncoder>> DEFAULT_ENCODERS = new HashMap<>();
static {
	DEFAULT_ENCODERS.put(BigDecimal.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(BigInteger.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(Blob.class, BlobValueEncoder::new);
	DEFAULT_ENCODERS.put(Boolean.class, BooleanValueEncoder::new);
	DEFAULT_ENCODERS.put(Byte.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(byte[].class, ByteArrayValueEncoder::new);
	DEFAULT_ENCODERS.put(Calendar.class, UtilCalendarValueEncoder::new);
	DEFAULT_ENCODERS.put(Clob.class, ClobValueEncoder::new);
	DEFAULT_ENCODERS.put(Date.class, SqlDateValueEncoder::new);
	DEFAULT_ENCODERS.put(java.util.Date.class, UtilDateValueEncoder::new);
	DEFAULT_ENCODERS.put(Double.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(Duration.class, DurationValueEncoder::new);
	DEFAULT_ENCODERS.put(Float.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(InputStream.class, InputStreamValueEncoder::new);
	DEFAULT_ENCODERS.put(Instant.class, InstantValueEncoder::new);
	DEFAULT_ENCODERS.put(Integer.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(LocalDate.class, LocalDateValueEncoder::new);
	DEFAULT_ENCODERS.put(LocalDateTime.class, LocalDateTimeValueEncoder::new);
	DEFAULT_ENCODERS.put(LocalTime.class, LocalTimeValueEncoder::new);
	DEFAULT_ENCODERS.put(Long.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(OffsetDateTime.class, OffsetDateTimeValueEncoder::new);
	DEFAULT_ENCODERS.put(OffsetTime.class, OffsetTimeValueEncoder::new);
	DEFAULT_ENCODERS.put(Reader.class, ReaderValueEncoder::new);
	DEFAULT_ENCODERS.put(Short.class, NumberValueEncoder::new);
	DEFAULT_ENCODERS.put(String.class, StringValueEncoder::new);
	DEFAULT_ENCODERS.put(Time.class, SqlTimeValueEncoder::new);
	DEFAULT_ENCODERS.put(Timestamp.class, SqlTimestampValueEncoder::new);
	DEFAULT_ENCODERS.put(ZonedDateTime.class, ZonedDateTimeValueEncoder::new);
}

与datetime相关的是SqlTimestampValueEncoder

SqlTimestampValueEncoder

image.png

getTimestamp

ResultSet的实现类是:com.mysql.cj.jdbc.result.ResultSetImpl,getTimestamp主要涉及两部分:

  • MysqlTextValueDecoder将原始报文字段解析为InternalTimestamp对象
  • SqlTimestampValueFactory将InternalTimestamp对象解析为应用使用的Timestamp

MysqlTextValueDecoder

image.png

SqlTimestampValueFactory

image.png

总结

以上是对数据库字段类型为datetime在新增、查询时候的一些逻辑,记录下来以备忘;
另外数据库字段类型为timestamp的在存储的时候还会有一次转换,使用的时候需要注意。

点赞收藏
大禹的足迹

在阿里搬了几年砖的大龄码农,头条号:大禹的足迹

请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

JDBC PreparedStatement 字段值为null导致TBase带宽飙升的案例分析

JDBC PreparedStatement 字段值为null导致TBase带宽飙升的案例分析

7
3