首页 > 其他分享 >SpringBoot+MyBatis Plus对Map中Date格式转换的处理

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

时间:2022-10-10 10:48:08浏览次数:71  
标签:Map SpringBoot class Date new 序列化 public ObjectMapper

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

问题

现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需要检索但是又需要结构化的存储, 会在数据库中产生很多 JSON 类型的字段, 与 Jackson 做对象的序列化和反序列化配合非常方便.

如果 JSON 都是类定义的, 这个序列化和反序列化就非常透明 -- 不需要任何干预, 写进去是什么, 读出来就是什么. 但是如果 JSON 在 Java 代码中是定义为一个 Map, 例如 Map<String, Object> 那么就有问题了, 对于 Date 类型的数据, 在存入之前是 Date, 取出来之后就变成 Long 了.

SomePO po = new SomePO();
//...
Map<String, Object> map = new HashMap<>();
map.put("k1", new Date());
po.setProperties(map);
//...
mapper.insert(po);
//...
SomePO dummy = mapper.select(po.id);
// 这里的k1已经变成了 Long 类型
Object k1 = dummy.getProperties().get("k1");

原因

不管是使用原生的 MyBatis 还是包装后的 MyBatis Plus, 在对 JSON 类型字段进行序列化和反序列化时, 都需要借助类型判断, 调用对应的处理逻辑, 大部分情况, 使用的是默认的 Jackson 的 ObjectMapper, 而 ObjectMapper 对 Date 类型默认的序列化方式就是取时间戳, 对于早于1970年之前的日期, 生成的是一个负的长整数, 对于1970年之后的日期, 生成的是一个正的长整数.

查看 ObjectMapper 的源码, 可以看到其对Date格式的序列化和反序列化方式设置于_serializationConfig 和 _deserializationConfig 这两个成员变量中, 可以通过 setDateFormat() 进行修改

public class ObjectMapper extends ObjectCodec implements Versioned, Serializable {
    //...
    protected SerializationConfig _serializationConfig;
    protected DeserializationConfig _deserializationConfig;
    //...

    public ObjectMapper setDateFormat(DateFormat dateFormat) {
        this._deserializationConfig = (DeserializationConfig)this._deserializationConfig.with(dateFormat);
        this._serializationConfig = this._serializationConfig.with(dateFormat);
        return this;
    }

    public DateFormat getDateFormat() {
        return this._serializationConfig.getDateFormat();
    }
}

默认的序列化反序列化选项, 使用了一个常量 WRITE_DATES_AS_TIMESTAMPS, 在类 SerializationConfig 中进行判断, 未指定时使用的是时间戳

public SerializationConfig with(DateFormat df) {
	SerializationConfig cfg = (SerializationConfig)super.with(df);
	return df == null ? cfg.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) : cfg.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}

实际的转换工作在 SerializerProvider 类中, 转换方法为

public final void defaultSerializeDateValue(long timestamp, JsonGenerator gen) throws IOException {
	if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
		gen.writeNumber(timestamp);
	} else {
		gen.writeString(this._dateFormat().format(new Date(timestamp)));
	}
}

public final void defaultSerializeDateValue(Date date, JsonGenerator gen) throws IOException {
	if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
		gen.writeNumber(date.getTime());
	} else {
		gen.writeString(this._dateFormat().format(date));
	}
}

解决

局部方案

1. 字段注解

这种方式可以用在固定的类成员变量上, 不改变整体行为

public class Event {
    public String name;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

另外还可以自定义序列化反序列化方法, 实现 StdSerializer

public class CustomDateSerializer extends StdSerializer<Date> {
    //...
}

就可以在 @JsonSerialize 注解中使用

public class Event {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

2. 修改 ObjectMapper

通过 ObjectMapper.setDateFormat() 设置日期格式, 改变默认的日期序列化反序列化行为. 这种方式只对调用此ObjectMapper的场景有效

private static ObjectMapper createObjectMapper() {
	ObjectMapper objectMapper = new ObjectMapper();
	SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	objectMapper.setDateFormat(df);
	return objectMapper;
}

因为 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

DateFormat is indeed cloned under the hood. Good suspicion there, but you are covered.

标签:Map,SpringBoot,class,Date,new,序列化,public,ObjectMapper
From: https://www.cnblogs.com/milton/p/16774777.html

相关文章

  • SpringBoot 整合邮件发送
    邮件发送更多参考:https://mrbird.cc/Spring-Boot-Email.html引入依赖在SpringBoot中发送邮件,需要用到spring-boot-starter-mail,引入spring-boot-starter-mail:<depend......
  • OpenGL之ShadowMap
    流程:先创建一个RenderTexture,然后用灯光的视口渲染。然后切换到正常相机,进行渲染,使用RenderTexture中的深度或者颜色纹理,然后还原当前顶点在灯光中的深度,两者对比,比缓存中......
  • JAVA中的Map简单使用
    Map的简述Map中得每个元素属于键值对模式。如果往map中添加元素时需要添加key和value.它也属于一个接口,该接口常见得实现类有:HashMap.Map中key有唯一性的特点,不能重......
  • MyBatis之ResultMap的association和collection标签详解
    一、前言MyBatis创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。我们希望每个数据库都具备良好的第三范式或BCNF范式,可惜它们并不都是那样。如果能......
  • Java开发学习(三十七)----SpringBoot多环境配置及配置文件分类
    一、多环境配置在工作中,对于开发环境、测试环境、生产环境的配置肯定都不相同,比如我们开发阶段会在自己的电脑上安装mysql,连接自己电脑上的mysql即可,但是项目开发完毕......
  • working copy is not up-to-date:SVN
    本文向大家讲解的是SVN提交错误:workingcopyisnotup-to-date解决方法:解决方法:在相应文件上,单击选择team,然后选择先更新,然后再提交。这样就好了。......
  • springboot整合mybatisPlus
    引入场景启动器              ......
  • Terms in Autosar -MemMap File / IDT / Ports & Port Interfaces
    MemMapFileMemMapfileisa headerfile (MemMap.h)usedtomapfunctionsorvariablestospecificmemorylocationsinFlashmemoryorRAM,toavoidwastage......
  • Date前端日期变为年龄处理方法
    现在页面展示效果为: 1,首先编写一个过滤器用来处理想要处理的数据 2,将要处理的数据绑定到过滤器上3,过滤器中:输入该代码即可完成效果(重点)filters:{showAge(value){......
  • Springboot日志记录方案
    目录​​一、概述​​​​二、市面上的日志框架以及日志抽象层类​​​​三、slf4j+Logback​​​​第一种:简单配置​​​​第二种:通过logback专有的xml配置文件详细配置​......