pg数据库保存本地时间时区丢失问题
pg数据库有timestamptz字段类型可以用来保存带时区的时间
在实体定义里采用OffsetDateTime 来定义字段
dao层使用mybatis-plus框架
写入指定时区的时间:"2024-04-05T00:00:00+02:00"
但是数据库里变成了 2024-04-05 06:00:00.000 +0800
很显然,时间被转成了+8时区,这不是我们希望的效果
为了解决此问题,在网上找了很多解决办法,其中较多的是说要自定义typehandler,于是我自定义了
OffsetDateTimeTypeHandler:
@Override
public void setNonNullParameter(PreparedStatement ps, int i, OffsetDateTime parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, java.sql.Timestamp.from(parameter.toInstant()));
}
测试发现没解决问题,查看Timestamp类的定义,发现他根本就不支持时区,所以必然导致时区丢失
后来又改为ps.setObject,
@Override
public void setNonNullParameter(PreparedStatement ps, int i, OffsetDateTime parameter, JdbcType jdbcType) throws SQLException {
//ps.setTimestamp(i, java.sql.Timestamp.from(parameter.toInstant()));
ps.setObject(i, parameter);
}
但是都不能解决问题。后来走读代码,发现mybatis自带的OffsetDateTimeTypeHandler也是这么写的,所以这里自定义typehandler不能解决问题
为了找到问题原因,我先用jdbc试一下
// 时间和时区
OffsetDateTime dateTimeWithZone = OffsetDateTime.parse("2024-04-04T00:00:00+02:00");
System.out.println(dateTimeWithZone);// 默认时区
Timestamp timestampWithZone = Timestamp.from(dateTimeWithZone.toInstant());
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO newtable (time) VALUES (?)")) {
// 设置时间戳
pstmt.setTimestamp(1, timestampWithZone);
//pstmt.setObject(1, dateTimeWithZone);
// 执行更新
pstmt.executeUpdate();
System.out.println("时间已保存到数据库");
} catch (SQLException e) {
e.printStackTrace();
}
image.png
调试发现代码走到这里已经丢失了时区
看Timestamp的定义,发现这个类根本就不支持带时区,所以如果用pstmt.setTimestamp来写,肯定会丢失时区
接下来尝改为setObject,发现最后写到数据库里的时间还是改变了时区
通过代码调试,在pg数据库驱动代码里,SimpleParameterList: writeV3Value
image.png
这里把时间字符串 2024-04-04 00:00:00+02 转成bytes保存了然后调用pgStream.send
可见数据库客户端发过去的是这样的字符串2024-04-04 00:00:00+02
那为什么最后数据库保存的变成了+8呢? 这就需要看数据库服务端的实现了
综上,jdbc写时间的时候,客户端侧并没有改变时区,所以问题还在服务端。因此,在应用层,mybatis层面是无法解决问题的,当前只能先在应用层规避,即自己去做时区转换。