SpringBoot+MyBatisPlus对Map中Date格式转换处理的方法详解

 更新时间:2022年10月10日 11:12:03   作者:Milton  
在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式。本文将为大家介绍一种方法:利用MyBatisPlus实现对Map中Date格式转换处理,需要的可以参考一下

在 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. 😃 – StaxMan Aug 2, 2013 at 19:43

3. 修改 SpringBoot 配置

增加配置 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss, 这种配置, 只对 Spring BeanFactory 中创建的 Jackson ObjectMapper有效, 例如 HTTP 请求和响应中对 Date 类型的转换

spring:
  ...
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

整体方案

国内项目, 几乎都会希望落库时日期就是日期的样子(方便看数据库表), 所谓日期的样子就是yyyy-MM-dd HH:mm:ss格式的字符串. 如果怕麻烦, 就通通都用这个格式了.

这样统一存在的隐患是丢失毫秒部分. 这个问题业务人员基本上是不会关心的. 如果需要, 就在格式中加上.

第一是 Spring 配置, 这样所有的请求响应都统一了

spring:
  ...
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

第二是定义一个工具类, 把 ObjectMapper 自定义一下, 这样所有手工转换的地方也统一了, 注意留一个getObjectMapper()方法

public class JacksonUtil {
    private static final Logger log = LoggerFactory.getLogger(JacksonUtil.class);
    private static final ObjectMapper MAPPER = createObjectMapper();
    private JacksonUtil() {}

    private static ObjectMapper createObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(df);
        return objectMapper;
    }

    public static ObjectMapper getObjectMapper() {
        return MAPPER;
    }
}

第三是启动后修改 MyBatisPlus 的设置, 即下面的 changeObjectMapper() 这个方法, 直接换成了上面工具类中定义的 ObjectMapper, 这样在 MyBatis 读写数据库时的格式也统一了.

@Configuration
@MapperScan(basePackages = {"com.somewhere.commons.impl.mapper"})
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

    @PostConstruct
    public void changeObjectMapper() {
        // This will unify the date format with util methods
        JacksonTypeHandler.setObjectMapper(JacksonUtil.getObjectMapper());
    }
}

到此这篇关于SpringBoot+MyBatisPlus对Map中Date格式转换处理的方法详解的文章就介绍到这了,更多相关SpringBoot MyBatisPlus处理Date格式转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot优化接口响应时间的九个技巧

    SpringBoot优化接口响应时间的九个技巧

    在实际开发中,提升接口响应速度是一件挺重要的事,特别是在面临大量用户请求的时候,本文为大家整理了9个SpringBoot优化接口响应时间的技巧,希望对大家有所帮助
    2024-01-01
  • Spring Boot 入门指南

    Spring Boot 入门指南

    Spring Boot 简化了Spring 应用的初始搭建、开发过程,开发人员可使用Spring提供的特定的方式来进行配置,不再需要重复自定义样板化的配置。本文就将带你入门Spring Boot
    2021-05-05
  • SpringBoot整合jasypt加密配置文件敏感信息

    SpringBoot整合jasypt加密配置文件敏感信息

    在项目中我们需要对配置文件的一些敏感信息进行加密处理,比如数据库账户密码,避免直接暴露出来,这种场景常常用于生产环境,我们不想让开发人员知道生产库的密码,有运维人员统一管理,所以本文给大家介绍了SpringBoot整合jasypt加密配置文件敏感信息
    2024-06-06
  • 详解RabbitMq如何做到消息的可靠性投递

    详解RabbitMq如何做到消息的可靠性投递

    这篇文章主要为大家介绍了RabbitMq如何做到消息的可靠性投递,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 基于编译虚拟机jvm—openjdk的编译详解

    基于编译虚拟机jvm—openjdk的编译详解

    下面小编就为大家分享一篇基于编译虚拟机jvm—openjdk的编译详解,具有很好的参考价值,希望对大家有所帮助
    2017-12-12
  • 微信小程序与Java后端接口交互

    微信小程序与Java后端接口交互

    本文主要介绍了微信小程序与Java后端接口交互,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Java利用Geotools实现不同坐标系之间坐标转换

    Java利用Geotools实现不同坐标系之间坐标转换

    GeoTools 是一个开源的 Java GIS 工具包,可利用它来开发符合标准的地理信息系统。本文将利用工具包Geotools实现不同坐标系之间坐标转换,感兴趣的可以了解一下
    2022-08-08
  • Java根据实体生成SQL数据库表的示例代码

    Java根据实体生成SQL数据库表的示例代码

    这篇文章主要来和大家分享一个Java实现根据实体生成SQL数据库表的代码,文中的实现代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-07-07
  • idea安装汉化插件的图文教程

    idea安装汉化插件的图文教程

    本文主要介绍了idea安装汉化插件的图文教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Java使用jxl库轻松玩转Excel表操作

    Java使用jxl库轻松玩转Excel表操作

    jxl 是一个非常实用的 Java 库,专门用于操作 Excel 表格,这篇文章主要为大家介绍了如何使用jxl进行Excel的基本操作,有需要的可以了解下
    2025-02-02

最新评论