SpringBoot+MyBatis Plus对Map中Date格式转换的处理

打印 上一主题 下一主题

主题 1031|帖子 1031|积分 3093

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式
问题

现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需要检索但是又需要结构化的存储, 会在数据库中产生很多 JSON 类型的字段, 与 Jackson 做对象的序列化和反序列化配合非常方便.
如果 JSON 都是类定义的, 这个序列化和反序列化就非常透明 -- 不需要任何干预, 写进去是什么, 读出来就是什么. 但是如果 JSON 在 Java 代码中是定义为一个 Map, 例如 Map 那么就有问题了, 对于 Date 类型的数据, 在存入之前是 Date, 取出来之后就变成 Long 了.
  1. SomePO po = new SomePO();
  2. //...
  3. Map<String, Object> map = new HashMap<>();
  4. map.put("k1", new Date());
  5. po.setProperties(map);
  6. //...
  7. mapper.insert(po);
  8. //...
  9. SomePO dummy = mapper.select(po.id);
  10. // 这里的k1已经变成了 Long 类型
  11. Object k1 = dummy.getProperties().get("k1");
复制代码
原因

不管是使用原生的 MyBatis 还是包装后的 MyBatis Plus, 在对 JSON 类型字段进行序列化和反序列化时, 都需要借助类型判断, 调用对应的处理逻辑, 大部分情况, 使用的是默认的 Jackson 的 ObjectMapper, 而  ObjectMapper 对 Date 类型默认的序列化方式就是取时间戳, 对于早于1970年之前的日期, 生成的是一个负的长整数, 对于1970年之后的日期, 生成的是一个正的长整数.
查看 ObjectMapper 的源码, 可以看到其对Date格式的序列化和反序列化方式设置于_serializationConfig 和 _deserializationConfig 这两个成员变量中, 可以通过 setDateFormat() 进行修改
  1. public class ObjectMapper extends ObjectCodec implements Versioned, Serializable {
  2.     //...
  3.     protected SerializationConfig _serializationConfig;
  4.     protected DeserializationConfig _deserializationConfig;
  5.     //...
  6.     public ObjectMapper setDateFormat(DateFormat dateFormat) {
  7.         this._deserializationConfig = (DeserializationConfig)this._deserializationConfig.with(dateFormat);
  8.         this._serializationConfig = this._serializationConfig.with(dateFormat);
  9.         return this;
  10.     }
  11.     public DateFormat getDateFormat() {
  12.         return this._serializationConfig.getDateFormat();
  13.     }
  14. }
复制代码
默认的序列化反序列化选项, 使用了一个常量 WRITE_DATES_AS_TIMESTAMPS, 在类 SerializationConfig 中进行判断, 未指定时使用的是时间戳
  1. public SerializationConfig with(DateFormat df) {
  2.         SerializationConfig cfg = (SerializationConfig)super.with(df);
  3.         return df == null ? cfg.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) : cfg.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  4. }
复制代码
实际的转换工作在 SerializerProvider 类中, 转换方法为
  1. public final void defaultSerializeDateValue(long timestamp, JsonGenerator gen) throws IOException {
  2.         if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
  3.                 gen.writeNumber(timestamp);
  4.         } else {
  5.                 gen.writeString(this._dateFormat().format(new Date(timestamp)));
  6.         }
  7. }
  8. public final void defaultSerializeDateValue(Date date, JsonGenerator gen) throws IOException {
  9.         if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
  10.                 gen.writeNumber(date.getTime());
  11.         } else {
  12.                 gen.writeString(this._dateFormat().format(date));
  13.         }
  14. }
复制代码
解决

局部方案

1. 字段注解

这种方式可以用在固定的类成员变量上, 不改变整体行为
  1. public class Event {
  2.     public String name;
  3.     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
  4.     public Date eventDate;
  5. }
复制代码
另外还可以自定义序列化反序列化方法, 实现 StdSerializer
  1. public class CustomDateSerializer extends StdSerializer<Date> {
  2.     //...
  3. }
复制代码
就可以在 @JsonSerialize 注解中使用
  1. public class Event {
  2.     public String name;
  3.     @JsonSerialize(using = CustomDateSerializer.class)
  4.     public Date eventDate;
  5. }
复制代码
2. 修改 ObjectMapper

通过 ObjectMapper.setDateFormat() 设置日期格式, 改变默认的日期序列化反序列化行为. 这种方式只对调用此ObjectMapper的场景有效
  1. private static ObjectMapper createObjectMapper() {
  2.         ObjectMapper objectMapper = new ObjectMapper();
  3.         SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  4.         objectMapper.setDateFormat(df);
  5.         return objectMapper;
  6. }
复制代码
因为 ObjectMapper 一般是当作线程安全使用的, 而 SimpleDateFormat 并非线程安全, 在这里使用是否会有问题? 关于这个疑虑, 可以查看 这个链接
@StaxMan: I am a bit concerned if ObjectMapper is still thread-safe after ObjectMapper#setDateFormat() is called. It is known that SimpleDateFormat is not thread safe, thus ObjectMapper won't be unless it clones e.g. SerializationConfig before each writeValue() (I doubt). Could you debunk my fear? – dma_k Aug 2, 2013 at 12:09
<blockquote>
DateFormat is indeed cloned under the hood. Good suspicion there, but you are covered.
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表