一次接口时间序列化返回时区错误

最近使用一个springboot项目采用默认的jackson序列化,配置参数如下

1
2
3
spring.jackson.default-property-inclusion=non_null
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.deserialization.read-unknown-enum-values-as-null=true

设置好后发现返回的Date类型,数据库时间是2023-11-06 16:24:40返回的时间是2023-11-06 08:24:40,时间少了8小时。
最开始以为是数据库采用PostgreSQL导致数据存储的时间有问题,查看数据库后数据正常。对数据进行debug后,找到是因为jackson对时间格式化时,采用的时区有误。

源码位置
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer#configureDateFormat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String dateFormat = this.jacksonProperties.getDateFormat();
if (dateFormat != null) {
try {
Class<?> dateFormatClass = ClassUtils.forName(dateFormat, null);
builder.dateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass));
}
catch (ClassNotFoundException ex) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
// Since Jackson 2.6.3 we always need to set a TimeZone (see
// gh-4170). If none in our properties fallback to the Jackson's
// default
TimeZone timeZone = this.jacksonProperties.getTimeZone();
if (timeZone == null) {
timeZone = new ObjectMapper().getSerializationConfig().getTimeZone();
}
simpleDateFormat.setTimeZone(timeZone);
builder.dateFormat(simpleDateFormat);
}
}

查看源码可知,配置格式化是支持配置DateFormat类的,也支持时间格式化配置。
如果使用时间格式化配置,默认取的是com.fasterxml.jackson.databind.cfg.BaseSettings#DEFAULT_TIMEZONE时区是UTC。导致序列化后的时间少了8小时。

解决办法:

Date改为LocalDateTime

修改序列号Date类型为LocalDataTime,类型改后配置的时间格式化会不生效,需要注册Bean来序列化LocalDataTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
@ConditionalOnProperty(name = "spring.jackson.date-format")
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(@Value("${spring.jackson.date-format}") String format) {
return builder -> {
//LocalDateTime
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
//DateTime
//DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//builder.serializers(new LocalDateSerializer(dateFormatter));
//builder.deserializers(new LocalDateDeserializer(dateFormatter));
};
}

添加时区配置

1
spring.jackson.time-zone=Asia/Singapore

添加对应时区配置即可。

注:
在使用IDEA连接PostgreSQL时,如果表字段设计的是默认current_timestamp,添加数据时会使用UTC时区,导致数据比实际时间少8小时。
解决办法,在连接配置中添加JVM参数,配置数据库连接时区。

1
-Duser.timezone=Asia/Singapore