记录一个springboot转json后出现的Date类型数据减少8小时的问题

568 阅读3分钟

1.问题记录

Mysql中数据如下,都是Date类型,实体对应数据类型也为Date

image.png

在业务中拿到的数据为:

image.png

转成json后:

image.png

两相对比,发现转成JSON后的时间数据相较于Mysql中存储的数据少了8小时

2.原因分析

可以发现,8小时正好为两个时区相差的时间。经过资料的搜索发现如下的原因

2.1 Mysql中存在着时区的概念

首先通过show variables like '%time_zone%'命令查询出Mysql的时区,得到结果如下

image.png

system_time_zone:mysql启动的时候,mysql服务所在服务器(主机)的时区,windows系统返回为空,linux系统一般会返回CST

time_zone:当前会话使用的时区,可以在配置文件中指定默认值,如果不指定,默认值是SYSTEM,表示使用system_time_zone指向的时区

像我这样的情况则是表示Mysql使用的时区为CST(CST可以同时表示美国,澳大利亚,中国,古巴四个国家的标准时间)

PS:建议直接使用set global time_zone = '+8:00'修改mysql全局时区为北京时间或者直接在mysql的配置文件my.ini中加上default-time_zone = '+8:00'

2.2 Jackson也有时区概念

Jackson是SpringBoot默认的Json视图转换框架。因为通常我们都会使用统一封装的方式,将code,data等数据封装成实体,交由SpringBoot框架自动转成Json,而SpringBoot就是默认使用JackSon进行对象序列化。

/**
 * Value that indicates that default {@link java.util.TimeZone}
 * (from deserialization or serialization context) should be used:
 * annotation does not define value to use.
 *<p>
 * NOTE: default here does NOT mean JVM defaults but Jackson databindings
 * default, usually UTC, but may be changed on <code>ObjectMapper</code>.
 */
public final static String DEFAULT_TIMEZONE = "##default";

注释已经说明默认情况下会将 时区设置为UTC 注意:Jackson反序列化时间类型的底层实际上调用的是Java的 SimpleDateFormat#parse() 方法,而JVM中的时区则会根据你的操作系统来获取,所以JVM认为你的时区应该是 GMT+8 时区

也就是说,在并没有其他配置的情况下,Jackson进行序列化操作时,默认的时区为UTC

2.3 各个时区缩写的含义(GMT,UTC,DST,CST,CET)及关系

UTC: Universal Time Coordinated 协调世界时,又称世界标准时间
GMT: Greenwich Mean Time 格林尼治平均时
CET:Central European Time 欧洲中部时间
CST时间: CST却同时可以代表如下 4 个不同的时区

  • Central Standard Time (USA) UT-6:00
  • Central Standard Time (Australia) UT+9:30
  • China Standard Time UT+8:00
  • Cuba Standard Time UT-4:00

关系:
UTC=GMT

CET=UTC/GMT + 1小时

CST=UTC/GMT +8 小时

CST=CET+7

3.问题总结

在第2点的分析中可以得知,我的数据库默认时区为CST,而在实体序列化成JSON时默认的时区为UTC。再由CST = UTC + 8这个关系,就可以明白为什么会出现1中的情况。

4.解决方式

4.1 修改mysql的时区

直接使用set global time_zone = '+8:00'修改mysql全局时区为北京时间或者直接在mysql的配置文件my.ini中加上default-time_zone = '+8:00'

4.2 在每个带有日期类型的字段上加上@JsonFormat

@NotNull 
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
@JsonProperty("start_time")
private Date contractEndDate;

4.3 在启动类或者带有@Configration注解的类中配置bean

@Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
        return jacksonObjectMapperBuilder ->
                jacksonObjectMapperBuilder.timeZone(TimeZone.getTimeZone("GMT+8"));
    }

4.4 在配置文件中配置Jackson相关配置(推荐和4.1一起使用)

spring:
    jackson:
         date-format: yyyy-MM-dd #如果使用字符串表示,用这行设置格式,可以自由设定
         time-zone: GMT+8 #设置时区

参考资料: