转载, 夏令时导致的时间问题

本文讲述了在项目中遇到的一个由于夏令时导致的日期转换错误问题。问题表现为数据库中用户生日日期与REST接口返回的日期不一致。经过排查发现,问题在于Jackson在将时间转换为JSON格式时,由于日期处于夏令时期间,导致时间快了一个小时。解决方案是保持时区设置的一致性。总结中强调了时间正确存储的重要性,推荐使用时间戳来避免时区问题。
摘要由CSDN通过智能技术生成

bug描述

问题起源于同事在项目中新增一个统计用户生日明细的接口,其中一个用户在数据库中的生日日期是“1988-07-29”,然而通过rest接口得到该用户的生日日期却为 “1988-07-28”。

环境说明

开始bug排查之前,先说明下项目环境:

  • 系统:centos 7.5
  • JDK:1.8.0_171
  • 技术栈:spring boot、Jackson、Druid、mybatis、oracle。

bug 排查

从数据层开始查找,先查询数据库时间和时区。

 
  1. SQL> SELECT SYSTIMESTAMP, SESSIONTIMEZONE FROM DUAL;

  2. SYSTIMESTAMP SESSIONTIMEZONE

  3. -------------------------------------------------------------------------------- ---------------------------------------------------------------------------

  4. 17-JUL-19 02.20.06.687149 PM +08:00 +08:00

  5.  
  6. SQL>

  7.  
  8.  

数据库时间和时区都没有问题。

确认操作系统和java进程时区

  • 查看操作系统时区
 
  1. [test@test ~]$ date -R

  2. Wed, 17 Jul 2019 16:48:32 +0800

  3. [test@test ~]$ cat /etc/timezone

  4. Asia/Shanghai

  5.  
  6.  
  • 查看java进程时区
 
  1. [test@test ~]$ jinfo 7490 |grep user.timezone

  2. user.timezone = Asia/Shanghai

  3.  
  4.  

可以看出我们操作系统使用的时区和java进程使用的时区一致,都是东八区。

用debug继续往上层查找查看mybatis和JDBC层

查看了问题字段mapper映射字段的jdbcType类型为jdbcType="TIMESTAMP",在mybatis中类型处理注册类TypeHandlerRegistry.java 中对应的处理类为 DateTypeHandler.java。

 
  1. this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));

  2.  
  3.  

进一步查看 DateTypeHandler.java 类:

 
  1. //

  2. // Source code recreated from a .class file by IntelliJ IDEA

  3. // (powered by Fernflower decompiler)

  4. //

  5.  
  6. package org.apache.ibatis.type;

  7.  
  8. import java.sql.CallableStatement;

  9. import java.sql.PreparedStatement;

  10. import java.sql.ResultSet;

  11. import java.sql.SQLException;

  12. import java.sql.Timestamp;

  13. import java.util.Date;

  14.  
  15. public class DateTypeHandler extends BaseTypeHandler<Date> {

  16. public DateTypeHandler() {

  17. }

  18.  
  19. public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {

  20. ps.setTimestamp(i, new Timestamp(parameter.getTime()));

  21. }

  22.  
  23. public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {

  24. Timestamp sqlTimestamp = rs.getTimestamp(columnName);

  25. return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;

  26. }

  27.  
  28. public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {

  29. Timestamp sqlTimestamp = rs.getTimestamp(columnIndex);

  30. return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;

  31. }

  32.  
  33. public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {

  34. Timestamp sqlTimestamp = cs.getTimestamp(columnIndex);

  35. return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;

  36. }

  37. }

  38.  
  39.  
  40.  

因为使用的数据源为Druid,其中 getNullableResult(ResultSet rs, String columnName) 方法参数中 ResultSet使用了DruidPooledResultSet.java 的 getTimestamp(String columnLabel) ,通过列名称获取值然后转换为Date类型的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值