问题:
往数据库插入数据时,发现时间比当前的时刻少了几个小时。
什么情况??
先来模拟一下事故现场
简单的设置一个时间,并插入数据库
@Service
public class Test {
@Autowired
TsMapper tsMapper;
@PostConstruct
public void init() throws ParseException {
TimeEntity timeEntity = new TimeEntity();
Date nowDate = new Date();
timeEntity.setTs(nowDate);
tsMapper.insert(timeEntity);
}
}
debug来看,时间是正确的。
再查看一下数据库的时间:
数据库时间比应用成面看到的时间少
怎么回事? 百度一下,发现是时区的问题。
使用【show variables like ‘%time_zone%’;】语句查看了数据库的时区。
为什么时区对应不上,时间就会出现不一致了?
带着这样的疑问,debug一下代码,最终发现,在拼接sql中的时间参数时,会调用
com.mysql.cj包下ClientPreparedQueryBindings类中的bindTimestamp方法
public void bindTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength, MysqlType targetMysqlType) {
x = TimeUtil.adjustNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs());
StringBuffer buf = new StringBuffer();
...
//使用带有分区的SimpleDateFormat对传进来的时间进行格式化
this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss",
targetMysqlType == MysqlType.TIMESTAMP && this.preserveInstants.getValue() ? this.session.getServerSession().getSessionTimeZone()
: this.session.getServerSession().getDefaultTimeZone());
buf.append(this.tsdf.format(x));
...
setValue(parameterIndex, buf.toString(), targetMysqlType);
}
其中tsdf是SimpleDateTime。
最终拼接成sql的时间,是通过simpleDateTime格式化Date得来的。
破案了,同样的时刻,由于SimpleDateFormat的时区不一致,导致格式化后的时间不一致,写入数据库的时间和应用程序时间出现不同。
SimpleDateTime的分区由什么决定?
这个分区当 JDBC 与 MySQL 开始建立连接时,如果没有设置JDBC连接串中的【serverTimezone】,则以数据库时区为准。
如何解决?
保证SimpleDateFormate的时区和应用程序时区一致
那如何控制SimpleDateFormat的时区?
非常简单,只需要再jdbc连接串中加上serverTimezone,指明时区为应用程序时区。例如:
jdbc:mysql://localhost:3306/sys?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
总结
数据库时间和应用程序不一致的原因
- 应用程序所在的时区和数据库不一致
- mybatisPlus填充时间参数时,采用SimpleDataFormat对时间进行格式化,由于SimpleDataFormat采用的数据库时区,导致数据不一致
有两种处理方法:
- 修改数据库时区,使其和应用程序时区保持一致
- jdbc连接串的时候,指定时区为应用程序时区