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

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

时间:2022-10-11 22:06:10浏览次数:88  
标签: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://blog.51cto.com/u_15773567/5744502

相关文章

  • mAP定义及相关概念
    mAP定义及相关概念P=>precision,即准确率R=>recall,即召回率PR曲线=>即以precision和recall作为纵、横轴坐标的二维曲线。一般来说,precision和recall......
  • springboot Druid后台监控功能和过滤
    @ControllerpublicclassDruidConfig{@ConfigurationProperties(prefix="spring.datasource")@BeanpublicDataSourcedruidDataSource(){return......
  • Map.Entry详解及List的流Stream
    Map.Entry详解Map是java中的接口,Map.Entry是Map的一个内部接口。Map提供了一些常用方法,如keySet()、entrySet()等方法。keySet()方法返回值是Map中key值的集合;entrySet(......
  • Springboot项目打war,jar包流程
    Springboot项目打jar包流程1、在POM中确定MAVEN打包插件已经引入<build>   <plugins>       <plugin>           <groupId>org.springframework.b......
  • Java集合TreeMap红黑树一生只爱一次(三天彻底理解应用TreeMap)
    一、那么为什么需要树呢?仔细想一下TreeSet、TreeMap,为什么要用他们。HashSet速度快,TreeSet则方便排序。HashMap速度快,TreeMap方便排序。同时,在树中查找数据项的速度和在有......
  • SpringBoot异步调用
    在程序执行时候还有一个瓶颈,串行执行,可以通过使用不同线程类快速提升应用的速度。要启用Spring的异步功能,必须要使用@EnableAsync注解。这样将会透明地使用java.util.conc......
  • HashMap实现原理及源码分析
    哈希表(hashtable)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现......
  • shell 知识点补充(4)-date/数值运算/test 指令/判断符号 [ ]/预设变数($0)/条件判断:if
    ​​1、date​linux时钟分为系统时钟(SystemClock)和硬件(RealTimeClock,简称RTC)时钟。系统时钟是指当前LinuxKernel中的时钟,而硬件时钟则是主板上由电池供电的时钟,这个硬件......
  • Mybatis实现@Select@Update等注解动态查询或更新SQL语句
    通过自己实现LanguageDriver,在服务器启动的时候,就会将我们自定义的标签解析为动态SQL语句。例如,写个构造updatein的动态sql更新,代码如下:packagecom.ljw.web.common.my......
  • springboot2 集成redis
    #redissettingsspring.redis.database=0spring.redis.host=127.0.0.1spring.redis.port=6379spring.redis.password=spring.redis.timeout=5000msspring.redis.let......